Java多线程

Posted yzg-14

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。

1. 串行并行并发

技术图片

2. 进程

技术图片

 3. 线程

技术图片

 技术图片

4. 创建线程的方式

A:创建线程并启动:继承Thread类

step1:创建子类,继承Thread类。
step2:重写run(),线程体。并发执行的内容,写在这个方法中。
step3:启动线程:start()

/*
class Cat
class Person

class MyException extends Exception{//异常类

}
class MyThread extends Thread{//线程类

}
*/
package com.qf.demo02;
//step1:创建子类,继承Thread类。
//step2:重写run(),线程体。并发执行的内容,写在这个方法中。
//step3:启动线程:start()
//1.子类继承Thread类
class MyThreadA extends Thread{
	@Override
	public void run() {
		//2.此处写要被并发执行的代码。。
		for(int i =0;i<100;i++){
			System.out.println("	t1..跳舞ing。。"+i);
		}
	}
}

class MyThreadB extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++){
			System.out.println("t2..唱歌ing。。"+i);
		}
	}
}
public class Test1Thread {

	public static void main(String[] args) {
		//需求:并发实现边唱边跳。。
		//创建线程的对象
		MyThreadA t1 = new MyThreadA();
		MyThreadB t2 = new MyThreadB();
		
		//对象访问方法,直接执行方法中内容。不是并发的范畴
//		t1.run();
//		t2.run();
		
//		for(int i = 0;i<100;i++){
//			System.out.println("		main...伴舞"+i);
//		}
		
		//3.启动线程?表示该线程一切准备就绪,随时可以被cpu执行了。但是cpu是否来执行你,不确定。。
		t1.start();//表示启动t1线程
		t2.start();//表示启动t2线程
		
		
		MyThreadA t3 = new MyThreadA();
		t3.start();//老子想重来一次。。可否啊?java.lang.IllegalThreadStateException
		
		
		
	}

}

B:实现Runnable接口,创建线程的方式二

step1:创建实现类,实现Runnable接口
step2:重写run(),线程体。并发执行的内容,写在这个方法中。
step3:启动线程:
	创建实现类的对象:mt
	根据实现类对象mt,创建Thread类对象t3,t4
	start()启动该线程:t3,t4
对比run()和start()
run(),是线程体。就是要并发执行内容。
start(),启动一个线程?就是该线程准备就绪了,随时可以被CPU执行。什么时候执行,CPU自己说了算。
一个线程,只能被start一次。
Thread类的构造方法
new Thread();//并发执行,执行run()方法。
new Thread(Runnable target);//并发执行,执行的run是Runnable接口的实现类实现run方法
package com.qf.demo02;
/*
step1:创建实现类,实现Runnable接口
step2:重写run(),线程体。并发执行的内容,写在这个方法中。
step3:启动线程:
 */


class MyThreadC implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<100;i++){
			System.out.println("	左手画圈。。。"+i);
		}
	}
	

}
class MyThreadD implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<100;i++){
			System.out.println("右手画方块。。"+i);
		}
	}
	
}
public class Test2Runnable {

	public static void main(String[] args) {
		MyThreadC mt1 = new MyThreadC();
		MyThreadD mt2 = new MyThreadD();
		
		
		Thread t3 = new Thread(mt1);//当启动t3线程,执行mt1对应的run方法
		Thread t4 = new Thread(mt2);//当启动t4线程,执行mt2对应的run方法
		
		t3.start();
		t4.start();
		
	}

}

两种启动方式比较

Thread类:JDK提供好的类,用于表示一个线程对象。实现类Runnable接口

run(),start()....

Runnable接口:定义了唯一的一个方法:run()——>线程体

方法一:直接继承Thread类

 step1:创建一个子类,来继承Thread类
 step2:重写run()方法,因为这是线程体:当CPU调度执行该线程的时候,就要执行的是run()方法中的代码。
 step3:创建该类的对象,表示一个线程,调用start()进而启动这个线程。意味着该线程一切准备就绪,随时可以被CPU调度执行。但是CPU是否立刻执行?不一定,要看CPU自己。

方法二:实现Runnable接口

 step1:创建一个实现类,实现Runnable接口
 step2:重写run()方法
 step3:先创建该实现类对象:mt,根据实现类对象再创建Thread对象,然后启动。

Thread类的构造方法:

 Thread();//创建一个线程对象,执行run()。线程的默认名:Thread-0,1,2...
 Thread(Runnable target);//创建一个线程对象,指明了target,执行的run是Runnable接口中。
 Thread(String name);//创建一个线程,并给起个名字
 Thread(Runnable target,String name);

对比两种创建并启动线程的方式:

 技术图片

5. 线程常用方法

1、获取当前的线程对象:由Thread类直接调用,获取当前正在被执行的那个线程
static Thread currentThread() ;//返回对当前正在执行的线程对象的引用。  
2、线程的名字:当一个线程创建的时候,如果没有设置名称:构造方法设置,或者setName()设置。系统默认的:Thread-0,Thread-1,Thread-2......
String getName() 
返回此线程的名称。 
void setName(String name) 
将此线程的名称更改为等于参数 name 。 
3、线程的Id:每个线程创建的时候,由系统自动分配一个Id,long类型的数值,终身不变。从线程的出生到死亡。
    该Id值,由系统自动分配,程序员无法手动操作。
long getId() 
返回此线程的标识符。 
4、线程的优先级:priority
    	System.out.println("最大优先级:"+Thread.MAX_PRIORITY);//10
		System.out.println("最小优先级:"+Thread.MIN_PRIORITY);//1
		System.out.println("正常优先级:"+Thread.NORM_PRIORITY);//5
当一个线程被创建的时候,由系统自动分配一个优先级,固定都是正常优先级:5
但是程序员可以根据需求,手动调整线程的优先级。
    int getPriority() 
	返回此线程的优先级 
    void setPriority(int newPriority) 
	更改此线程的优先级。 
    
有个坑:容易误解为:优先级别高的先执行,然后再执行优先级低的。大大的错误XXXXX
    优先级别高,被CPU调度执行的机会就多。但是不绝对。
    优先级别低,被CPU执行的机会就少,但是也不绝对。
    
5、线程的睡眠:重要的方法
    static void sleep(long millis) 
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
    静态方法,应该由类直接调用,对象也可以调用,有坑:不是谁调用就谁睡,而是当前正在执行的线程进入睡眠了。和谁调用无关。
阻塞:
    
6、线程合并
    void join() 
	等待这个线程死亡。 
   t1线程,t2线程,main线程
    t1,t2,main--->3条线程抢占资源
    	某一个时刻:main线程中:t1.join(),主线程要等待t1线程死亡之后再执行
    t1,t2--->2条线程抢占资源,main等
    t1结束后,main线程再执行
    
    
阻塞:
    Scanner scan = new Scanner(System.in);
	scan.nextInt();//阻塞式。等--->解除阻塞,读取到一个键盘输人
7、守护线程
    setDaemon();
为前台线程服务,如果所有的前台线程都结束了,那么守护线程也就结束了。
    
GC:垃圾自动回收机制。JVM启动后,创建主线程执行main()的时候。。。随之而创建并启动的还有很多后台线程,比如gc()
8、其他方法:
package com.qf.demo02;
class MyThreadF extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++){
			System.out.println("	"+Thread.currentThread().getName()+"	"+i);
		}
	}
}
public class Test7Join {

	public static void main(String[] args) {
		MyThreadF t1 = new MyThreadF();
		MyThreadF t2 = new MyThreadF();
		t1.setName("线程1");
		t2.setName("线程2");
		t1.start();
		t2.start();
		for(int i =0;i<100;i++){
			System.out.println("main..."+i);
			if(i == 50){
				try {
					/*
					 * 线程合并
    					void join() 
					等待这个线程死亡。
					
					主线程中:t1.join(),主线程如果持有资源,会将机会让给t1线程。
						t1线程在main线程之前执行。t1结束后,main线程才会执行。
					 */
					t1.join();//?
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

}
package com.qf.demo02;
class MyThreadG extends Thread{
	private int i=0;
	public void run() {
		while(true){
			System.out.println(Thread.currentThread().getName()+"	"+i);
			i++;
		}
	}
}
public class Test8Daemon {

	public static void main(String[] args) {
		MyThreadG t1 = new MyThreadG();
		//将t1线程设置为守护线程
		t1.setDaemon(true);
		
		t1.start();
		
		for(int i=0;i<1000;i++){
			System.out.println("	main.."+i);
		}
	}

}

6. 线程的状态

线程的生命周期:5种,6种,7种。

线程的一生,就好比秀女的一声。

线程new出来:新建

准备就绪,启动:start:就绪状态

如果被CPU调度执行:运行状态,run()方法

阻塞状态:-->进入就绪

出生-->就绪-->运行-->死亡

技术图片

技术图片

 7. 临界资源的安全问题

多个线程访问共享的数据,临界资源。

多个线程之间存在共享的数据。一条线程执行过程中,其他线程也可以访问,可能会修改数据的值。造成的共享数据的不安全。叫做临界资源的安全问题。

package com.qf.demo03;
class MyThread1 implements Runnable{

	private  int tickets = 10;
	
	private Object obj = new Object();//成员变量,属于对象
	@Override
	public void run() {
		
		while(true){
			/*
			 * 需要使用synchronized同步代码块,防止共享数据存在不安全性。
			 * 
			 * 锁定的对象:谁作为锁头?
			 * 	this:mt
			 * 	obj:
			 *  "abc":所谓的大招,字面常量,内存中独一份。
			 */
			//t1,t2,t3,t4
			synchronized (this) {//任意对象,只要是这4个线程共同的对象即可。  t1给obj上锁
				if(tickets > 0){
					try {
						Thread.sleep((int)(Math.random()*101));//t1睡去了。。释放cpu资源
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+",出售:"+tickets);
					tickets--;
				}else{
					System.out.println(Thread.currentThread().getName()+",售票结束。。");
					break;
				}
			}
			
			
		}
	}
	
}
public class Test9_SaleTicket {

	public static void main(String[] args) {
		/*
		 * 2.模拟火车站卖票:使用4个线程,模拟4个售票口,出售共同的100张票。
				分别使用两种创建线程的方式实现。
				
			思路:
				四个售票口:4条线程:t1,t2,t3,t4
				
				出售100张票:int ticket:100,99,98,97.。。。。1

		 */
		MyThread1 mt = new MyThread1();

		Thread t1 = new Thread(mt, "售票口1");
		Thread t2 = new Thread(mt, "售票口2");
		Thread t3 = new Thread(mt, "售票口3");
		Thread t4 = new Thread(mt, "售票口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

同步synchronized

同步的方式一:同步代码块

synchronized(锁对象){//上锁
	//被同步的代码,每次只能被一个线程执行,中间不能被其他线程插入执行
}//锁打开

注意点:

同步的原理:锁定一个对象。(对象可以和程序无关,但必须是多个线程访问的共同的对象才可以)。

常用的锁对象:

this,创建一个对象,传入进去。

大招:类名.class,字符串常量:"abc"

package com.qf.demo03;
class MyThread2 extends Thread{
	private static int ticket = 10;
	
//	private static Object obj = new Object();//成员变量
	
	private Object obj;
	
	public  MyThread2(String name,Object obj) {
		super(name);
		this.obj = obj;
	}
	@Override
	public void run() {
		while(true){
			/*
			 * 如果是继承Thread类的方式,不能用this作为锁对象,因为创建多个子线程对象,this其实指代的是多个对象,不是同一个,锁不住
			 * 
			 * 大招:
			 * 		字符串常量:"abc"
			 * 		类名.class  
			 * 
			 * java程序:源代码-->class
			 * 		    进行编译:class字节码文件
			 *        万事万物皆对象:字节码文件---->类和它对应的:Class
			 *        
			 *        
			 *        
			 *        Class类
			 *        	对象.getClass()-->Class
			 *        	类名.class-->Class
			 *        	Class.forName("包名.类名")-->Class
			 */
			synchronized (MyThread2.class) {
				if(ticket > 0){
					try {
						Thread.sleep((int)(Math.random()*100));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+",出售:	"+ticket);
					ticket--;
				}else{
					System.out.println(Thread.currentThread().getName()+",售票结束。。");
					break;
				}
			}
			
		}
	}
}
public class Test10_SaleTicket {

	public static void main(String[] args) {
		
		Object obj = new Object();//作为锁对象
		
		MyThread2 t1 = new MyThread2("售票口1",obj);
		MyThread2 t2 = new MyThread2("售票口2",obj);
		MyThread2 t3 = new MyThread2("售票口3",obj);
		MyThread2 t4 = new MyThread2("售票口4",obj);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

同步的方式二:同步方法

//该方法,每次只能有一个线程来执行,期间,不能被其他的线程插入执行。
public synchronized void 方法名(){

} 

同步的优缺点:

  • 解决了多线程之间的共享数据的安全问题

  • 降低效率,容易死锁。

死锁:多个线程互相持有对象,僵持的现象。

解决死锁:

1、减少成员变量的使用。

2、加大锁的粒度。不要锁小对象,锁大对象。

package com.qf.demo03;

class MyThread3 implements Runnable{
	private static String bread = "面包";
	private static String milk = "牛奶";
 	
	boolean flag ;//true, false

	@Override
	public void run() {
		if(flag == true){//t1
			synchronized (bread) {
				System.out.println(Thread.currentThread().getName()+"	,已经拥有了"+bread+",还想要。。"+milk);
				
				synchronized (milk) {
					System.out.println(Thread.currentThread().getName()+",两者都拥有了。。");
				}
			}
		}else{//t2
			synchronized (milk) {
				System.out.println(Thread.currentThread().getName()+"	,已经拥有了"+milk+",还想要。。"+bread);
				
				synchronized (bread) {
					System.out.println(Thread.currentThread().getName()+",两者都拥有了。。");
				}
			}
		}
	}
	
}
public class Test11_DeadLock {

	public static void main(String[] args) {
/*
 * t1:张三
 * t2:李四
 * 		t1锁定break,要milk
 * 		t2锁定milk,要bread
 */
		MyThread3 mt1 = new MyThread3();
		MyThread3 mt2 = new MyThread3();
		
		mt1.flag = true;
		mt2.flag = false;
		Thread t1 = new Thread(mt1,"张三");
		Thread t2 = new Thread(mt2,"李四");
		t1.start();
		t2.start();
			
	}

}

技术图片

 

以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程与并发库高级应用-工具类介绍

多线程 Thread 线程同步 synchronized

Java多线程具体解释

自己开发的在线视频下载工具,基于Java多线程

什么是JAVA的多线程?

多个用户访问同一段代码