前言
多线程作为编程语言中的难点,虽然初级程序员可能很少遇到线程相关的开发任务,但是作为程序员,持续学习和保持对编程的热爱,要求我们对于线程也需要有一定的认识。
本篇博客介绍Java中创建线程的4种方式,并进行了简单的对比;介绍了线程的生命周期,几个关键方法的作用;然后阐述了线程的三大特性,最后结合Java集合框架分析了线程安全的问题。
其他关于Java线程的文章如下:
- Java进阶(5)——创建多线程的方法extends Thread和implements Runnable的对比 & 线程池及常用的线程池
- Java进阶(6)——抢购问题中的数据不安全(非原子性问题)& Java中的synchronize和ReentrantLock锁使用 & 死锁及其产生的条件
文章目录
- 前言
- 引出
- 一、创建多线程的方式
- 1、继承Thread类
- 当前线程:Thread.currentThread()
- 2、实现Runable接口
- 3、实现Callable接口
- 4、线程池
- 二、线程的生命周期
- join():运行结束再下一个
- yield():暂时让出cpu的使用权
- deamon():守护线程,最后结束
- sleep():如果有锁,不会让出
- 三、线程的三大特性
- 原子性:AtomicInteger
- CAS
- 可见性:加volatile关键字
- 有序性:引用有了,对象还没
- 四、集合中的线程安全问题
- 1、List集合的问题
- 用vector解决
- 用Collections.synchronizedList
- 2、表格总结
- 3、关于hash算法
- 总结
引出
1.线程创建的方式,继承Thread类,实现Runable接口,实现Callable接口,采用线程池;
2.线程生命周期: join():运行结束再下一个, yield():暂时让出cpu的使用权,deamon():守护线程,最后结束,sleep():如果有锁,不会让出;
3.线程3大特性,原子性,可见性,有序性;
4.list集合中线程安全问题,hash算法问题;
一、创建多线程的方式
1、继承Thread类
调用的流程:
- 调用start()
- 线程处于准备状态,一旦cup有闲的时间片,
- 让线程调用run()方法
package com.tianju.threadLearn;
public class ThreadA extends Thread{
@Override
public void run(){
System.out.println("我是线程A:"+this.getName());
}
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.setName("线程A");
threadA.start(); // 我准备好了
String name = Thread.currentThread().getName();
System.out.println(name);
// 获得当前线程的路径
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
System.out.println(path);
}
}
当前线程:Thread.currentThread()
2、实现Runable接口
和继承Thread相比,这个用的更多,因为Java是单继承的,只能继承一个,而可以实现多个接口,所以更加灵活
package com.tianju.threadLearn;
/**
* 这个常用,因为java是单继承,如果继承了extends Thread;
* 就不能继续继承了;
*/
public class ThreadB implements Runnable{
private String name;
public ThreadB(String name){
this.name = name;
}
@Override
public void run() {
for(int i =0;i<10;i++){
System.out.println("当前线程:"+this.name);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
new Thread(new ThreadB("B")).start();
}
}
3、实现Callable接口
package com.tianju.threadLearn;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Callable,可以有返回值
*/
public class ThreadC implements Callable<Long> {
@Override
public Long call() throws Exception {
long sum = 0;
for (int i = 0; i < 500000; i++) {
sum+=i;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Long> threadC = new FutureTask<Long>(new ThreadC());
Thread thread = new Thread(threadC);
thread.start();
Long aLong = threadC.get();
System.out.println(aLong);
}
}
4、线程池
池化技术pool【常量池、数据连接池、线程池】
package com.tianju.threadLearn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolDemo1 {
public static void main(String[] args) {
System.out.println("===============缓存线程池================");
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<10;i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
});
}
System.out.println("===============固定线程池================");
ExecutorService executorService2 = Executors.newFixedThreadPool(1);
for (int i=0;i<10;i++) {
executorService2.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
});
}
}
}
二、线程的生命周期
join():运行结束再下一个
package com.tianju.threadLearn;
public class ThreadA1 extends Thread{
static int c = 0;
@Override
public void run(){
for (int i = 0; i < 20; i++) {
try {
System.out.println(this.getName()+": "+i);
sleep(300);
c++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA1 a = new ThreadA1();
a.setName("A");
ThreadA1 b = new ThreadA1();
b.setName("B");
a.start();
a.join(); // A运行结束,B才能开始运行
b.start();
b.join(); // 控制执行顺序
System.out.println(Thread.currentThread().getName());
System.out.println("c的结果为: "+c);
}
}
yield():暂时让出cpu的使用权
package com.tianju.threadLearn;
/**
* yield,让出cpu的使用权
*/
public class ThreadA2 extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
try {
if (i%2==0){
// 让出cpu的使用权,避免一个线程一直占有cpu,防止独占cpu
// 重新竞争
yield();
}
System.out.println(this.getName()+": "+i);
sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA2 a = new ThreadA2();
a.setName("A");
ThreadA2 b = new ThreadA2();
b.setName("B");
a.start();
b.start();
System.out.println(Thread.currentThread().getName());
}
}
deamon():守护线程,最后结束
package com.tianju.threadLearn;
/**
* 守护线程
* 用户线程
*
*/
public class ThreadA3 extends Thread{
@Override
public void run(){
while (true){
System.out.println("我是守护线程.......");
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA3 a = new ThreadA3();
a.setName("A");
a.setDaemon(true); // 守护线程,用户线程main结束后,他就结束了
a.start();
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread()+":"+i);
Thread.sleep(200);
}
}
}
sleep():如果有锁,不会让出
package com.tianju.threadLearn;
public class ThreadSleep extends Thread{
@Override
public void run(){
for (int i=0;i<10;i++){
System.out.println("我是线程A:"+this.getName());
try {
sleep(200); // 不会让出锁
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
ThreadSleep threadA = new ThreadSleep();
threadA.setName("线程A");
threadA.start(); // 我准备好了
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
三、线程的三大特性
原子性:AtomicInteger
package com.tianju.tx;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解决原子性:AtomicInteger
*/
public class AtomicDemo1 {
static AtomicInteger x= new AtomicInteger();
static class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
x.incrementAndGet();
System.out.println(getName()+ "--x:"+x);
try {
sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
new A().start();
new A().start();
new A().start();
}
}
CAS
CAS(Compare And Swap)
- 从主存拷贝到工作内存(线程)
- 修改,和主存比较,如果如果和取时结果一致,刷新到主存中。
ABA问题:
如果红色线程拿到了主存中的3,进行加1,;
蓝色线程也拿到了主存中的3,进行了加1,然后又减1;
红色线程一直问主存里面是不是3,然后发现还是3,就把4写到主存里面;
但是此时的3已经被蓝色线程动过了;
解决方案是,加一个版本号,每次被操作,就让版本号加1
可见性:加volatile关键字
package com.tianju.view;
/**
* 线程之间不可见
* volatile 用于解决可见性
*/
public class VisibleDemo2 {
volatile static boolean f = true; // 处理可见性,解决不了原子性
static class A extends Thread{
@Override
public void run() {
while (f){
}
System.out.println("A的f为:"+f);
}
}
static class B extends Thread{
@Override
public void run() {
f=false;
System.out.println("B设置f为:"+f);
}
}
public static void main(String[] args) throws InterruptedException {
new A().start();
Thread.sleep(300);
new B().start();
}
}
有序性:引用有了,对象还没
假设: 编译器修改顺序 1 , 3, 2
线程A执行到对象初始化阶段,还没有初始化;
线程B先获得的对象的引用,然后调用对象,
但是对象还没有初始化,因此会报错,对象初始化错误
四、集合中的线程安全问题
1、List集合的问题
package com.tianju.collection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class ListDemo {
static List<Integer> list = new ArrayList<>();
// 有序性+原子性保证
volatile static AtomicInteger index =new AtomicInteger(0);
static class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
list.add(index.incrementAndGet());
}
}
}
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
A a2 = new A();
A a3 = new A();
a1.start();
Thread.sleep(500);
a2.start();
a3.start();
System.out.println(list);
}
}
用vector解决
package com.tianju.collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
public class VectorDemo {
static Vector<Integer> list = new Vector<>();
// 有序性+原子性保证
volatile static AtomicInteger index =new AtomicInteger(0);
static class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
list.add(index.incrementAndGet());
}
}
}
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
A a2 = new A();
A a3 = new A();
a1.start();
a1.join(); // A运行结束,B才能开始运行
a2.start();
a2.join();
a3.start();
a3.join();
System.out.println(list);
}
}
用Collections.synchronizedList
package com.tianju.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
public class CollectionDemo {
static List<Integer> ls = new ArrayList<>();
static List<Integer> list = Collections.synchronizedList(ls);
// 有序性+原子性保证
volatile static AtomicInteger index =new AtomicInteger(0);
static class A extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
list.add(index.incrementAndGet());
}
}
}
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
A a2 = new A();
A a3 = new A();
a1.start();
a1.join(); // A运行结束,B才能开始运行
a2.start();
a2.join();
a3.start();
a3.join();
System.out.println(list);
}
}
2、表格总结
序号 | 线程不安全 | 线程安全 |
---|---|---|
1 | ArrayList | Vector/ Collections.synchronizedList(list) |
2 | HashMap | ConcurentHashMap/HashTable |
3 | HashSet | Collections.synchronizedSet() |
Hashset的底层是HashMap
3、关于hash算法
比如一开始有蓝色点,经过hash算法之后,得到结果3,然后放到相应的位置;
然后又来了一个红色的点,经过hash算法之后,也是3,此时就顺着蓝色的位置去找;
之前是采用头插法,就是新来的放到蓝色的之前,在1.8之后改成尾插法,放到蓝色的后面;
如果又有新来的,就继续往后面加,此时出现了不利的情况,就是越来越多成了一个很长的链表;
所以就采用了树的结果,这样提高的查找的效率;在1.8中采用的红黑树;
总结
1.线程创建的方式,继承Thread类,实现Runable接口,实现Callable接口,采用线程池;
2.线程生命周期: join():运行结束再下一个, yield():暂时让出cpu的使用权,deamon():守护线程,最后结束,sleep():如果有锁,不会让出;
3.线程3大特性,原子性,可见性,有序性;
4.list集合中线程安全问题,hash算法问题;