JAVA多线程的总结学习-基础
程序员文章站
2022-05-04 18:09:36
...
一、概念
1、进程与线程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
(线程是cpu调度的最小单位)
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。
2、线程实现方式
(1)继承Thread类
(2)实现Runable接口
(3)实现Callable接口,配合FutureTask使用
3、Thread和Runable的区别总结:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
4、实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
5、线程生命周期
(1)新建状态(New):新创建了一个线程对象。
(2)就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
(3)运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
(4)阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
a、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
b、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
c、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程 终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
6、线程中方法介绍
(1)调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。线程的优先级用整数表示,取值范围是1~10。
Thread类有以下三个静态常量:
static int MAX_PRIORITY:线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY: 线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY:分配给线程的默认优先级,取值为5。
方法: setPriority()设置线程的优先级
getPriority()获取线程的优先级
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
(2)线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
(3)线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
(4)线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
(5)线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
(6)线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
二、例子
1、继承Thread类
package thread;
//通过继承Thread 实现线程
public class MyThread extends Thread{
private int i=0;
public void run(){
System.out.println("in MyThread");
for(i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==25){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
}
}
说明:
继承Thread,重写run方法
直接new对象MyThread,start启动线程
2、实现Runnable接口
package thread;
//通过实现Runnable接口, 实现线程
public class MyRunnable implements Runnable{
private int i = 0;
//private Boolean stopFlag = false;
@Override
public void run() {
System.out.println("in MyRunnable");
for(i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
/*
public void stopThread(){
this.stopFlag = true;
}*/
public static void main(String[] args) {
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==15){
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
}
}
}
}
3、实现Callable接口
package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//通过实现Callable接口, 实现线程
public class MyCallable implements Callable<Integer>{
private int i = 0;
@Override
public Integer call() throws Exception {
int sum = 0;
for(i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+i);
sum +=i;
}
return sum;
}
public static void main(String[] args) {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> ft = new FutureTask<Integer>(c);
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
Thread t = new Thread(ft);
t.start();
}
}
System.out.println("FOR循环执行完毕=======");
try {
int sum = ft.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
说明:
实现Callable接口,实现call方法
需要FutureTask配合使用
4、线程同步synchronized
package thread;
//线程同步
public class ThreadTest extends Thread{
private int count = 5;
//延伸问题,竞争锁(当其中一个线程释放锁时,其他同时进行竞争,导致cpu瞬间飚满。。。。)
public void run(){
count--;
System.out.println(Thread.currentThread().getName()+"=== count:"+count);
}
public static void main(String[] args){
ThreadTest t = new ThreadTest();
Thread t1 = new Thread(t,"t1");
Thread t2 = new Thread(t,"t2");
Thread t3 = new Thread(t,"t3");
Thread t4 = new Thread(t,"t4");
Thread t5 = new Thread(t,"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
t2=== count:3
t3=== count:2
t5=== count:1
t4=== count:0
t1=== count:3
t3=== count:2
t5=== count:1
t4=== count:0
t1=== count:3
在run方法上加上synchronized修饰,运行结果如下:
t2=== count:4
t5=== count:3
t4=== count:2
t3=== count:1
t1=== count:0
t5=== count:3
t4=== count:2
t3=== count:1
t1=== count:0
说明:加上synchronized,count顺序执行了,因为加了synchronized,一个线程进入就锁死了,得释放时,其他线程才能再进入,再运行
5、多个对象,多个线程synchronized
package thread;
//多个对象,多个线程,同步锁
public class MultiThread {
private static int num = 0;
public synchronized void printNum(String tag){
if("a".equals(tag)){
num = 100;
System.out.println("tag="+tag+"---start---");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num = 200;
System.out.println("tag="+tag+"---start---");
}
System.out.println("num="+num);
}
public static void main(String[] args){
//两个不同的对象
final MultiThread m1 = new MultiThread();
final MultiThread m2 = new MultiThread();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
m1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
m2.printNum("b");
}
});
t1.start();
t2.start();
}
}
tag=a---start---
tag=b---start---
num=200
num=200
tag=b---start---
num=200
num=200
说明:虽然已经加上了synchronized关键字修饰,但是因为M1、m2是两个不同的对象,他们对应的t1和t2是分别的线程,所以只加synchronized,并不能锁定住两个对象的 执行顺序
在printNum方法上,加上static关键字,运行结果如下
tag=a---start---
num=100
tag=b---start---
num=200
num=100
tag=b---start---
num=200
说明:静态方法加上synchronized关键字,则线程调用方法时,获得的是类级别的锁,而不是方法级别的锁
6、锁重入
package thread;
/**
* 锁重入
* 每个方法都加了synchronized关键字
* 方法1里调用了方法2,方法2里调用了方法3
* 当一个线程获取了一个对象的锁后,当再次请求这个对象时,可以再次获得该对象的锁
*/
public class SyncDubbo1 {
public synchronized void method1(){
System.out.println("method1.....");
method2();
}
public synchronized void method2(){
System.out.println("method2.....");
method3();
}
public synchronized void method3(){
System.out.println("method3.....");
}
public static void main(String[] args) {
final SyncDubbo1 sd = new SyncDubbo1();
Thread t = new Thread(new Runnable(){
@Override
public void run() {
sd.method1();
}
});
t.start();
}
}
运行结果:
method1.....
method2.....
method3.....
method2.....
method3.....
7、脏读
package thread;
/**
* 脏读
*/
public class DirtyRead {
private String name="zhangsan";
private String pwd = "123";
public synchronized void setValue(String name, String pwd){
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
System.out.println("当前的用户:"+name+",密码:"+pwd);
}
public void getValue(){
System.out.println("当前的用户:"+name+",密码:"+pwd);
}
public static void main(String[] args){
final DirtyRead dr = new DirtyRead();
Thread t = new Thread(new Runnable(){
@Override
public void run() {
dr.setValue("lisi", "456");
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dr.getValue();
}
}
运行结果:
当前的用户:lisi,密码:123
当前的用户:lisi,密码:456
当前的用户:lisi,密码:456
问题:已经变更了用户和密码,但是根据运行结果知道,密码并未变更
给getValue()方法加上synchronized关键字,运行结果如下:
当前的用户:lisi,密码:456
当前的用户:lisi,密码:456
当前的用户:lisi,密码:456
说明:
* DirtyRead类里有两个方法:setValue、getValue
* 当只给setValue加synchronized时,出现脏读现象
* 所以脏读情况出现,是因为没有考虑整体的一个同步加锁情况
* 避免脏读,给getValue方法加上synchronized
* 当只给setValue加synchronized时,出现脏读现象
* 所以脏读情况出现,是因为没有考虑整体的一个同步加锁情况
* 避免脏读,给getValue方法加上synchronized
8、wait、notify线程应用
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* wait/notify 实现了线程之间的通信
* wait/notify必须配合synchronized块使用
* wait释放锁,notify不释放锁
*
* 因为notify不释放锁,会造成不实时的问题
* 解决方法:CountDownLatch(并发工具类),使用时不需要synchronized区块
*
*/
public class WaitNotifyThread {
private static List list = new ArrayList();
public static void add(){
list.add("11111");
}
public int getSize(){
return list.size();
}
public static void main(String[] args) {
final WaitNotifyThread wnt = new WaitNotifyThread();
final Object lock = new Object();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
synchronized (lock) {
for(int i=0;i<10;i++){
wnt.add();
System.out.println("当前线程"+Thread.currentThread().getName()+"添加一个元素");
if(wnt.getSize() == 5){
lock.notify();
System.out.println("唤醒线程2...");
}
}
}
}
},"t1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
synchronized (lock) {
if(wnt.getSize() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("已唤醒,list大小=5,终止...");
throw new RuntimeException();
}
}
},"t2");
t2.start();
t1.start();
}
}
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
唤醒线程2...
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
已唤醒,list大小=5,终止...
Exception in thread "t2" java.lang.RuntimeException
at thread.WaitNotifyThread$2.run(WaitNotifyThread.java:64)
at java.lang.Thread.run(Unknown Source)
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
唤醒线程2...
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
当前线程t1添加一个元素
已唤醒,list大小=5,终止...
Exception in thread "t2" java.lang.RuntimeException
at thread.WaitNotifyThread$2.run(WaitNotifyThread.java:64)
at java.lang.Thread.run(Unknown Source)
说明:当容器大小=5时,唤醒线程2.
问题:因为notify不释放锁,会造成不实时的问题,用CountDownLatch(并发工具类)解决问题,使用时不需要synchronized区块
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* wait/notify 实现了线程之间的通信
* wait/notify必须配合synchronized块使用
* wait释放锁,notify不释放锁
*
* 因为notify不释放锁,会造成不实时的问题
* 解决方法:CountDownLatch(并发工具类),使用时不需要synchronized区块
*
*/
public class WaitNotifyThread {
private static List list = new ArrayList();
public static void add(){
list.add("11111");
}
public int getSize(){
return list.size();
}
public static void main(String[] args) {
final WaitNotifyThread wnt = new WaitNotifyThread();
final CountDownLatch cdl = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<10;i++){
wnt.add();
System.out.println("当前线程"+Thread.currentThread().getName()+"添加一个元素");
if(wnt.getSize() == 5){
cdl.countDown();
System.out.println("唤醒线程2...");
}
}
}
},"t1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
if(wnt.getSize() != 5){
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("已唤醒,list大小=5,终止...");
throw new RuntimeException();
}
},"t2");
t2.start();
t1.start();
}
}
9、用wait/notify模拟queue
package thread;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 用wait/notify模拟queue(队列)
* 队列:LinkedBlockingQueue ArrayBlockingQueue
* 方法:put:向队列里放对象,当队列长度达到上限时,put方法阻塞等待,直到再有空间,才执行
* take:从队列里取对象,当队列里空的时候,take方法阻塞等待,直到队列里再加入新的对象,再执行
*/
public class WaitNotifyQueue {
//创建一个容器队列
private LinkedList list = new LinkedList();
//创建一个计数器
private AtomicInteger count = new AtomicInteger(0);
//上限、下限设置
private final int minSize = 0;
private final int maxSize ;
private WaitNotifyQueue(int size){
this.maxSize = size;
}
private final Object lock = new Object();
//put方法
public void put(Object o){
synchronized (lock) {
if(count.get() == this.maxSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(o);
count.incrementAndGet();
lock.notify();
System.out.println("成功增加一个对象"+o);
}
}
public Object take(){
Object o = null;
synchronized (lock) {
if(count.get() == this.minSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o = list.removeFirst();
count.decrementAndGet();
lock.notify();
System.out.println("成功的移除了对象"+o);
}
return o;
}
public static void main(String[] args) {
final WaitNotifyQueue wtq = new WaitNotifyQueue(5);
wtq.put("a");
wtq.put("b");
wtq.put("c");
wtq.put("d");
wtq.put("e");
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
wtq.put("f");
wtq.put("g");
}
},"t1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
wtq.take();
wtq.take();
}
},"t2");
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
10、volatile声明线程变量
package thread;
/**
* volatile 声明一个变量在多个线程间可见
* 原因:jdk1.5之后,为Thread单独分配一个内存空间,存储线程用到的变量等内容,
* 当外部修改变量时,线程内存空间内的变量值并没有进行修改,所以代码一直执行
*
*/
public class VolatileThread {
private static volatile boolean flag = true;
public void testVolatile(){
System.out.println("---------start-----------");
while(flag){
}
System.out.println("----------end-------------");
}
public static void main(String[] args) {
final VolatileThread vt = new VolatileThread();
Thread t = new Thread(new Runnable(){
@Override
public void run() {
vt.testVolatile();
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("flag=="+flag);
}
}
11、volatile 不具有synchronized关键字的原子性DEMO
package thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* volatile 不具有synchronized关键字的原子性
* 用atomic类库(原子类)使用,本身支持多线程并发的原子操作
* 解析代码:如果volatile 具有原子性,那么count的值最终必定为10000(1000×10)
* 但是运行结果是随机的
* 用Atomic原子类,最终结果是正确的
*/
public class VolatileNoAtomicThread extends Thread{
//private static Integer count = 0;
private static AtomicInteger count = new AtomicInteger(0);
public static void addCount(){
for(int i=0;i<1000;i++){
//count++;
count.incrementAndGet();
}
System.out.println("count:"+count);
}
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomicThread[] arr = new VolatileNoAtomicThread[10];
for(int i=0;i<10;i++){
arr[i] = new VolatileNoAtomicThread();
}
for(int i=0;i<10;i++){
arr[i].start();
}
}
}
下一篇: Java并发编程之CAS算法