Java之多线程详解

1.什么是多线程?
线程其实就是程序执行的一条路径 一个进程可以包含多个线程
多线程是并发进行的
底层 其实还是单线程(cpu运行效率极其高 )
并发:两个任务同时请求运行 而处理器只能执行一个 不过由于处理的特别快感觉是两个任务同时运行
并行:就是两个任务一起同步运行(需要多核cpu)

2.java程序运行原理
jvm是多线程的 有两个线程: 垃圾回收线程 主函数main是个线程

3.Thread类
继承Thread
重写run 方法
调用start方法

public class Main{ 
	public static void main(String args[]) throws Exception{
		 
		 MyThread mt = new MyThread();
		 mt.start();
		 
		 for(int i=1;i<=1000;i++) {
			 System.out.println("主线程代码");
		 }
	 }
}
class MyThread extends Thread{
	public void run()
	{
		for(int i=1;i<=1000;i++) {
			 
			System.out.println("我的线程代码");
		 }
	}
}

4.实现Runnable
实现Runnable接口
重写run方法
因为要启动线程必须通过Thread类的start方法才能使用!
所以必须要创建Thread的对象 把Runable作为target参数传递给Thread

public class Main{ 
	public static void main(String args[]) {
		demo de = new demo();
		new Thread(de).start();    //1.第一种启动线程的方法  匿名对象
		
		Thread t = new Thread(de); //2.第二种启动线程的方法
		t.start();
		
		Runnable run = de;         //3.第三种启动线程的方法 父类引用指向子类对象
		new Thread(run).start();

		for(int i=1;i<=1000;i++) {
			 System.out.println("主线程代码");
		 }
	}
}
class demo implements Runnable
{
	@Override
	public void run() {
		for(int i=1;i<=1000;i++) {
			 System.out.println("我的线程代码");
		 }
	}
}

Runnable能够实现多线程的原理 (源码分析)
通过作为Thread类的构造参数 传入底层的init方法 最后给Runnable 类型的target赋值 然后去调用run方法

两者的区别:
1.继承 代码比较简单 不过有父类的话就不用了
2.实现 代码比较复杂 可以多实现
个人而言喜欢使用Runnable接口 扩展性比较强
当然具体情况具体分析

6.用匿名内部类实现多线程

public class Main{ 
	public static void main(String args[]) {
		new Thread() {
			public void run()
			{
				for(int i=1;i<=1000;i++) {
					 System.out.println("线程1111111111代码");
				 }
			}
		}.start();
		
		new Thread(new Runnable() {
			public void run()
			{
				for(int i=1;i<=1000;i++) {
					 System.out.println("线程33333333333代码");
				 }
			}
		}).start();
	}	
}

7.Thread的常用方法
1.给线程设置名字

public class Main{ 
	public static void main(String args[]) {
		
		//1.通过String name的构造来取名字
		new Thread("线程1") {
			public void run()
			{
					 System.out.println(this.getName()+"...线程1111111111代码");
			}
		}.start();
		
		new Thread("线程2") {
			public void run()
			{
					 System.out.println(this.getName()+"...线程2222222222代码");
			}
		}.start();
		
		//2.通过Thread类的setName方法
		new Thread() {
			public void run()
			{
				 this.setName("线程3");
				 System.out.println(this.getName()+"...线程33333333代码");
			}
		}.start();
		
		Thread t1 = new Thread() {
			public void run()
			{
					 System.out.println(this.getName()+"...线程4444444代码");
			}
		};
		t1.setName("线程4");
		t1.start();
	}	
}
}

2.获取当前线程的引用

public class Main{ 
	public static void main(String args[]) {
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+"...线程111111");
			}
		}).start();
		
		System.out.println(Thread.currentThread().getName()+"...线程222222");
	}
}

3.休眠线程
异常处理只能自己处理
因为父类的run方法没有抛出 所以子类的方法一定不能抛出!!
sleep方法是自行调用处理
wait方法是必须由别人调用才能处理

public class Main{ 
	public static void main(String args[]) {
		//倒计时器 
		new Thread() {
			@Override
			public void run() {
				
				for(int i=10;i>0;i--) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("倒计时"+i+"秒");
				}
			}
		}.start();
	}
}

4.守护线程
守护线程就是例如象棋中的车马炮
而被守护的线程就是象棋中的帅 如果当帅的线程结束了 那么整个守护线程就立马结束(但是实际情况中是守护线程还是会执行一下 有遗留的数据要处理)
本demo中 t1为非守护线程

public class Main{ 
	public static void main(String args[]) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				
				for(int i=2;i>0;i--) {
					System.out.println("线程1....111");
				}
			}
		};
		
		Thread t2 = new Thread() {
			@Override
			public void run() {
				
				for(int i=10;i>0;i--) {
					System.out.println("线程2....222");
				}
			}
		};
		
		t2.setDaemon(true);
		t1.start();
		t2.start();
	}
}

5.加入线程
相当于插队 !
就是线程1在运行的时候 线程2插队进来 等线程2结束后 线程1才继续执行
匿名内部类在使用所在方法中的局部变量时该变量必须要使用final修饰!或者事实上为final的

public class Main{ 
	public static void main(String args[]) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				
				for(int i=10;i>0;i--) {
					System.out.println("线程1....1111111111111111");
				}
			}
		};
		
		Thread t2 = new Thread() {
			@Override
			public void run() {
				
				for(int i=10;i>0;i--) {
					if(i==8) {
						try {
							t1.join(); //t1进来插队
							//t1.join(1); //t1进来插队 1毫秒以后  t1 t2又继续交替运行
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					System.out.println("线程2....222");
				}
			}
		};
		
		t1.start();
		t2.start();
	}
}

6.礼让线程
效果不是很明显 了解即可
7.设置线程的优先级
效果也不是很明显

public class Main{ 
	public static void main(String args[]) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				
				for(int i=100;i>0;i--) {
					System.out.println("线程1....1111111111111111");
				}
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				
				for(int i=100;i>0;i--) {
					System.out.println("线程2....22");
				}
			}
		};
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.MIN_PRIORITY);
		
		t1.start();
		t2.start();
	}
}

8.同步代码块
锁要一致
好处是 在每个任务的多条执行命令下不会错乱 线程之间的逻辑不会相互影响 造成乱码或者错误异常
例如本demo中的打印可能会出现 打印不正确的情况 虽然概率低不过存在
为了保证以上错误不会出现 所以要加锁!保证线程安全

public class Demo1_Synchronized {

	/**
	 * @param args
	 * 同步代码块
	 */
	public static void main(String[] args) {
		final Printer p = new Printer();
		
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
	}

}

class Printer {
	Demo d = new Demo();
	public void print1() {
		//synchronized(new Demo()) {							//同步代码块,锁机制,锁对象可以是任意的
		synchronized(d) {
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
		}
	}
	
	public void print2() {
		//synchronized(new Demo()) {							//锁对象不能用匿名对象,因为匿名对象不是同一个对象
		synchronized(d) {		
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}
}

class Demo{}

使用静态同步方法

class Printer2 {
	Demo d = new Demo();
	//非静态的同步方法的锁对象是神马?
	//答:非静态的同步方法的锁对象是this
	//静态的同步方法的锁对象是什么?
	//是该类的字节码对象
	public static synchronized void print1() {							//同步方法只需要在方法上加synchronized关键字即可
		System.out.print("黑");
		System.out.print("马");
		System.out.print("程");
		System.out.print("序");
		System.out.print("员");
		System.out.print("\r\n");
	}
	
	public static void print2() {
		//synchronized(new Demo()) {							//锁对象不能用匿名对象,因为匿名对象不是同一个对象
		synchronized(Printer2.class) {		
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}
}

使用非静态同步方法

class Printer2 {
	Demo d = new Demo();
	//非静态的同步方法的锁对象是神马?
	//答:非静态的同步方法的锁对象是this
	//静态的同步方法的锁对象是什么?
	//是该类的字节码对象
	public  synchronized void print1() {							//同步方法只需要在方法上加synchronized关键字即可
		System.out.print("黑");
		System.out.print("马");
		System.out.print("程");
		System.out.print("序");
		System.out.print("员");
		System.out.print("\r\n");
	}
	
	public  void print2() {
		//synchronized(new Demo()) {							//锁对象不能用匿名对象,因为匿名对象不是同一个对象
		synchronized(this) {		
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}
}

9.线程安全问题
第一种用Thread类继承

public class Demo3_Ticket {

	/**
	 * 需求:铁路售票,一共100张,通过四个窗口卖完.
	 */
	public static void main(String[] args) {
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
	}

}

class Ticket extends Thread {
	private static int ticket = 100;    //静态共享
	//private static Object obj = new Object();		//如果用引用数据类型成员变量当作锁对象,必须是静态的
	public void run() {
		while(true) {
			synchronized(Ticket.class) {
				if(ticket <= 0) {
					break;
				}
				try {
					Thread.sleep(10);				//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "...这是第" + ticket-- + "号票");
			}
		}
	}
}

第二种用Runnable

public class Demo4_Ticket {

	/**
	 * @param args
	 * 火车站卖票的例子用实现Runnable接口
	 */
	public static void main(String[] args) {
		MyTicket mt = new MyTicket();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		
		/*Thread t1 = new Thread(mt);				//多次启动一个线程是非法的
		t1.start();
		t1.start();
		t1.start();
		t1.start();*/
	}

}

class MyTicket implements Runnable {
	private int tickets = 100;
	@Override
	public void run() {
		while(true) {
			synchronized(this) {
				if(tickets <= 0) {
					break;
				}
				try {
					Thread.sleep(10);				//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
			}
		}
	}
}

10.多线程死锁问题
互相不肯释放
所以避免死锁 同步代码块不要嵌套

public class Demo5_DeadLock {

	/**
	 * @param args
	 */
	private static String s1 = "筷子左";
	private static String s2 = "筷子右";

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s1) {
						System.out.println(getName() + "...获取" + s1 + "等待" + s2);
						synchronized(s2) {
							System.out.println(getName() + "...拿到" + s2 + "开吃");
						}
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s2) {
						System.out.println(getName() + "...获取" + s2 + "等待" + s1);
						synchronized(s1) {
							System.out.println(getName() + "...拿到" + s1 + "开吃");
						}
					}
				}
			}
		}.start();
	}
}

Collections 可以调用方法始集合都可以变成线程安全
所以说Vector和Hashtable都被淘汰了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑瞳丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值