多线程
进程与线程
进程:系统资源分配的单位。包含多个线程
线程:CPU的调度和执行单位。
一个进程至少包含一个线程。
线程的创建一共有三种方式:
方式一:
继承Thread类,重写run方法,调用start方法启动线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
TestThread1 testThread1=new TestThread1();
testThread1.start();
//主线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程"+i);
}
}
}
方式二:
实现Runnable接口,重写run方法,调用start方法启动线程(推荐使用此方法,因为java是单继承,但可以实现多个接口)
public class TestRunable implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestRunable testRunable=new TestRunable();
//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(testRunable).start();
//主线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程"+i);
}
}
}
方式三:
实现Callable接口。实现Callable接口需要返回值类型,重写call方法需要抛出异常。
/**
* callable的好处
* 可以定义返回值
* 可以抛出异常
*/
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
WebDownLoader webDownLoader=new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载图片名l"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1=new TestCallable("https://pic88.com/pic/12020080510373687334.html","1.jpg");
TestCallable t2=new TestCallable("https://pic88.com/pic/120200717072546119636.html","2.jpg");
TestCallable t3=new TestCallable("https://pic88.com/pic/120200805204640200949.html","3.jpg");
//创建执行服务
ExecutorService ser= Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1=ser.submit(t1);
Future<Boolean> r2=ser.submit(t2);
Future<Boolean> r3=ser.submit(t3);
//获取结果
boolean rs1=r1.get();
boolean rs2=r2.get();
boolean rs3=r3.get();
System.out.println(rs1); System.out.println(rs2); System.out.println(rs3);
//关闭服务
ser.shutdown();
}
}
class WebDownLoader{
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载异常");
}
}
}
龟兔赛跑案例:
//案例龟兔赛跑
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if(Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean falg=gameover(i);
//如果比赛结束则停止
if(falg){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
public boolean gameover(int j){
if(winner!=null){
return true;
}else{
if(j==100){
winner=Thread.currentThread().getName();
System.out.println("胜者"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race=new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
龟兔赛跑案例因为兔子的偷懒导致乌龟最终胜利,我们通过定制相同赛道,使用sleep方法模拟兔子睡觉,然后乌龟取得胜利。
线程状态:
线程的状态可以分为:
1、创建:创建线程对象开始表示线程已经创建。
2、就绪:调用start()方法进入到就绪状态,但并没有立即调度。
3、阻塞:当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。
4:运行:进入运行状态,线程才真正执行线程体的代码块。
5、死亡:线程中断或者结束,一旦进入死亡状态,就不能再次启动。
线程中有几个常用方法:
这里就不再一一演示。
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待…
public class TestDaemon {
public static void main(String[] args) {
God god=new God();
You you=new You();
Thread thread=new Thread(god);
thread.setDaemon(true);//默认为false表示为用户线程
thread.start();//上帝线程启动
new Thread(you).start();//用户线程启动
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你!");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你快乐的生活着!");
}
System.out.println("goodbye world!");//嗝屁了
}
}
线程同步
多个线程操作一个资源
并发:多个线程同时操作同一个资源
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象﹒这时候我们就需要线程同步﹒线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"我").start();
new Thread(station,"你").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;
boolean flag=true;//外部停止方式
@Override
public void run() {
//多人买票
while(flag){
buy();
}
}
private void buy(){
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"张票");
}
}
多线程并发就会引发线程安全问题,上面这个例子就是线程不安全的,会造成数据库脏读等问题;java也提供了解决线程不安全问题提供了synchronized关键字
synchronized 实现原理:队列和锁
两种实现形式:同步方法和同步块
每个对象对应一把锁
弊端:方法里面需要修改的内容才需要锁,锁的过多,浪费资源
锁的对象即变量,需要增删改的对象
对于普通同步方法,锁是当前实例对象。 如果有多个实例 那么锁对象必然不同无法实现同步。
对于静态同步方法,锁是当前类的Class对象。有多个实例 但是锁对象是相同的 可以完成同步。
对于同步方法块,锁是Synchonized括号里配置的对象。对象最好是只有一个的 如当前类的 class 是只有一个的 锁对象相同 也能实现同步。
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"我").start();
new Thread(station,"你").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;
boolean flag=true;//外部停止方式
@Override
public void run() {
//多人买票
while(flag){
buy();
}
}
private synchronized void buy(){
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"张票");
}
}
public class UnsafeBank {
public static void main(String[] args) {
Account account=new Account(1000,"金婚基金");
Drawing you=new Drawing(account,50,"你");
Drawing girlFriend=new Drawing(account,100,"girlFriend");
you.start();
girlFriend.start();
}
}
//账户
class Account{
int money;
String name;
public Account(int money,String name){
this.money=money;
this.name=name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
@Override
public void run(){
//synchronized 因为有两个银行对象,这里锁的是银行类
synchronized(account){
//判断是否有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"余额不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money=account.money-drawingMoney;
//手里的金额
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
}
锁:(互斥锁)synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的。到了Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。
Lock锁
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
ReentrantLoc(可重入锁)类实现了lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁.
synchronized与Lock的对比:
1、Lock是显示锁(手动开启和关闭锁),synchronized是阴式锁,出了作用域自动释放
2、Lock只有代码块锁,synchronized有同步代码块和同步方法
3、使用lock锁,jvm将花费较少的时间来调度线程,性能更好
4、优先使用顺序:Lock>同步代码块>同步方法
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2=new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums=10;
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try{
lock.lock();//加锁
if(ticketNums>0){
System.out.println(ticketNums--);
}else{
break;
}
}finally {
lock.unlock();//解锁
}
}
}
}
死锁问题
既然有锁,那肯定会出现死锁问题;死锁就是多个对象争夺同同一资源。
死锁有四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
只要破坏其中一个就可以解决死锁问题
public class DeadLock {
public static void main(String[] args) {
Makeup g1=new Makeup(0,"小红帽");
Makeup g2=new Makeup(1,"大红帽");
g1.start();
g2.start();
}
}
class Lipstick{}//口红
class Mirror{}//镜子
class Makeup extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;//选择
String girlName;//使用者
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
if(choice==0){
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
}
}else{
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
}
}
}
}
线程协作
生产者消费者问题
解决方式一:
并发协作模型“生产者/消费者模式”—>管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
- 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer=new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费者消费了"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id){
this.id=id;
}
}
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
//容器计数器
int count=0;
public synchronized void push(Chicken chicken){
//判断容器是否满了,如果满了通知消费者消费
if(count==chickens.length){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们需要丢入产品
chickens[count]=chicken;
count++;
this.notifyAll();
}
public synchronized Chicken pop(){
//判断能否消费
if(count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
方式二:
并发协作模型“生产者/消费者模式”—>信号灯法
public class TestPc2 {
public static void main(String[] args) {
TV tv=new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("动画片");
}else{
this.tv.play("新闻");
}
}
}
}
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
class TV{
String voice;
boolean flag=true;
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了"+voice);
this.notifyAll();
this.voice=voice;
this.flag=!this.flag;
}
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了"+voice);
this.notifyAll();
this.flag=!this.flag;
}
}
线程池
思路:提前创建好多个线程,放入线程池中,使用时,直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。
好处:
1、提高了响应速度(减少了创建新线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3、便于线程管理
public class TestPool {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
完毕~