欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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()方法,该线程结束生命周期
                           JAVA多线程的总结学习-基础

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();
	}
}

运行结果:count并没有一次递减
t2=== 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
说明:加上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();
	
	}
}

运行结果:线程t1对应的num值未打印
tag=a---start---
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
说明:静态方法加上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.....

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
问题:已经变更了用户和密码,但是根据运行结果知道,密码并未变更

给getValue()方法加上synchronized关键字,运行结果如下:
当前的用户:lisi,密码:456
当前的用户:lisi,密码:456
说明:
* DirtyRead类里有两个方法:setValue、getValue
 * 当只给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)
说明:当容器大小=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 线程