多线程基础[Java]

进程和线程

进程是一个应用程序;线程是应用程序中的执行单元。
一个进程可以由多个线程,最基本的包括主线程和垃圾回收线程。
一个线程占一个栈空间,不同栈空间相互独立,互不共享,堆和方法区内存是对这些栈是共享的。JVM内存结构
所以可以看出,main函数(主线程)结束不代表线程结束,因为还有其它分支线程进行弹栈压栈操作。

线程的创建方法

1.创建一个Thread类的子类,并重写run()方法
2.实现Runnable接口,重写run()方法,并用new Thread(实现接口的对象)进行封装
3.使用匿名类的形式快速实现Runnable接口,并进行封装

线程的运行状态

五种状态:新建状态——就绪状态——运行状态——阻塞状态——死亡状态。
new Thread()标志着为新建状态;
start()标志着进入就绪状态,又叫做可执行状态,拥有抢夺CPU时间片的权利(执行权),当抢到了一块CPU时间片之后,就开始执行run方法;
run()标志着进入运行状态,当这个时间片用完之后会再次回到就绪状态,然后重新抢夺时间片并继续run方法的运行(run的内容不会再次从头开始运行);
遇到阻塞事件(用户输入或者sleep)标志着进入阻塞状态,放弃当前的时间片,回到就绪状态重新夺取时间片;
run()的内容运行完之后就进行死亡状态。

线程的sleep,interrupt和终止线程

sleep()函数是静态方法,作用是对当前线程(所处的代码块决定)进行休眠。interrupt()函数是非静态方法,可以中断线程中的休眠时间这种方式是通过Java的异常处理机制来实现的。
真正意义上的线程终止是通过添加一个布尔标记来终止线程的。

		......
		rm.run=false;//终止t4线程
		......		
class MyRunnable implements Runnable{
	boolean run=true;//布尔标记
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if (run) {
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName() + "--->" + i);
			}
		}else {
			//save
			return;
		}
	}
}

多线程并发

真正的多线程并发是指同一时间点有多个线程同时运行。对于单核CPU一次只能运行一个线程,系统会对多个线程进行调度,在线程之间来回切换,在宏观上看来像是在同时运行。

并发: 当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行: 当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

package birda;
//创建线程的三种方法

public class Mythread extends Thread{		
	@Override
	public void run() {
		for(int i=0;i<1000;i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//继承Thread
		Mythread t1=new Mythread();
		t1.setName("t1线程");
		t1.start();
		for(int i=0;i<1000;i++) {
			System.out.println("主线程--->"+i);
		}
		
		try {
			Thread.sleep(1000*5);//当前线程睡眠;此时当前线程为主线程
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		System.out.println("开始执行t2线程的内容");
		//实现Runnable接口
		Thread t2=new Thread(new MyRunnable());
		t2.setName("t2线程");
		t2.start();
				
		try {
			Thread.sleep(1000*5);//当前线程睡眠;此时当前线程为主线程
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		System.out.println("开始执行t3线程的内容");		
		//匿名类的形式创建线程
		Runnable r=new Runnable() {
			@Override
			public void run() {

				for (int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread().getName() + "--->" + i);
				}
				
				try {
					Thread.sleep(1000 * 60);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block

				}

				System.out.println("已经被睡眠60s了");

				if (Thread.interrupted()) {
					System.out.println("线程被中断过");
				}
			}						
		};
		
		Thread t3=new Thread(r);
		t3.setName("线程t3");
		System.out.println("线程t3的名字为: "+t3.getName());
		t3.start();
		//中断线程睡眠
		t3.interrupt();
		
		//终止线程
		//通过布尔标记的方式终止
		MyRunnable rm=new MyRunnable();
		Thread t4=new Thread(rm);
		t3.setName("线程t4");
		t3.start();
		//主线程睡眠5秒
		try {
			Thread.sleep(100*5);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		rm.run=false;//终止t4线程
					
	}
}

class MyRunnable implements Runnable{
	boolean run=true;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if (run) {
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName() + "--->" + i);
			}
		}else {
			//save
			return;
		}
	}
}

线程调度

1.线程调度模型
抢占式调度模型:优先级高的线程抢夺的时间片大概率是多的(并非所有时候都这样)【Javja采用的调度机制】
均分式调度模型:每一个线程所占用的时间片的多少是相等的。

2.Java中的调度方法
实例方法:
getPriority()和setPriority()分别为得到线程优先级和设置优先级
join()是让当前线程进入阻塞状态,先运行调度该方法的线程

静态放法
yield()是让当前线程从运行状态进入就绪状态

package birda;
public class Mythread extends Thread{		
	@Override
	public void run() {
	//	Thread.currentThread().yield();
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
	
	@SuppressWarnings("static-access")
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//继承Thread
		Thread t1=new Mythread();
		t1.setName("t1线程");
		t1.start();
		//设置优先级		
		for(int i=0;i<10;i++) {
			Thread.currentThread().yield();//线程让位一次,从运行状态进入就绪状态
			System.out.println("主线程--->"+i);
		}
				
		try {
			Thread.sleep(1000*2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(t1.getPriority());
		
		//创建第二个线程
		Runnable r=new MyRunnable();
		Thread t2=new Thread(r);
		t2.setName("t2线程");
		t2.start();
		System.out.println(t2.getPriority());		
	}	
}

class MyRunnable implements Runnable {
	@Override
	public void run() {			
		Thread t3=new Mythread();
		t3.setName("t3线程");
		t3.start();
		try {
			t3.join();//当前线程受阻塞,先运行t3再运行其它线程
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i+" ");
			//System.out.println(Thread.currentThread().getName());
		}		
	}	
}

线程并发带来的安全问题

线程产生安全问题的原因是什么:两个线程a,b同时访问一个数据m。b应该在a执行完成之后访问m。但是如果ab是并发的,线程a会改变m的值,存在一种可能就是线程b访问的是最开始的那个m。
存在线程安全问题的三个条件:
1.线程是并发的
2.共享数据
3.数据有修改的行为

看下面的程序代码:

public class Threadsafe2 extends Thread{
	private Account account;
	
	public Threadsafe2(Account t) {
		this.account=t;
	}
	
	@Override
	public void run() {
		account.withdraw(5000);
		System.out.println(Thread.currentThread().getName()+":取款金额为:"+5000+" 余额:"+account.getbalance());
	}

	public static void main(String[] args) {		
		Account account=new Account("account-001",10000);
		Threadsafe2 t1=new Threadsafe2(account);
		t1.setName("线程t1");
		t1.start();
		
		Threadsafe2 t2=new Threadsafe2(account);
		t2.setName("线程t2");
		t2.start();		
	}
}

//另一块代码
public class Account {
	private String account;
	private int accountmoney;
	
	public Account(String name,int money) {
		this.account=name;
		this.accountmoney=money;
	}
		
	//取钱
	public void withdraw(int m) {
		int before=accountmoney;
		int after =before-m;	
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		this.getmoney(after);			
	}
	
	//显示账户余额
	public void getmoney(int n) {
		accountmoney=n;
	}
	
	public int getbalance() {
		return accountmoney;
	}	
}
线程t1:取款金额为:5000 余额:5000
线程t2:取款金额为:5000 余额:5000

线程同步

线程同步机制就是线程按照先后顺序执行,这样会牺牲一部分的效率
线程异步就是多个线程直接按相互独立,执行过程互不影响,就是线程并发。

synchronized代码块

synchronized代码块的用法是占用一个对象锁,只让当前占用这个锁的线程运行,其他用到这个锁的线程排队等待。

synchronized(m){
//m是需要排队的线程的共享变量
}

当m为this的时候:

//整体代码与上面相同,修改的部分如下:
public void withdraw(int m) {
		synchronized (this) {
			int before = accountmoney;
			int after = before - m;
			try {
				Thread.sleep(100);//睡眠的目的是不及时更新余额,制造安全问题
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.getmoney(after);
		}
	}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0

this在这里面的指的是Account account=new Account("account-001",10000);
每一个对象有一把锁,在这里面account是被t1和t2共享的,那么只能有一个线程占用这把锁,当这个所被占用后,其他线程就需要等待。

锁池lockpool:线程进入锁池找共享对象的对象锁,此时会释放之前占有的CPU时间片,接下来有两种可能,如果找到了就进入就绪状态重新抢夺时间片,如果没找到那就在锁池中继续等待。

当m为成员变量时

public void withdraw(int m) {
		synchronized (account) {
			int before = accountmoney;
			int after = before - m;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.getmoney(after);
		}
	}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0

成员变量account是属于共享对象Account account=new Account("account-001",10000);的,而这个对象是共享的,那么account也是被共享的。

当m为常量时

public void withdraw(int m) {
		synchronized ("123") {
			int before = accountmoney;
			int after = before - m;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.getmoney(after);
		}
	}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0

当m为字符串常量时一定会发生线程同步,并且是对全部线程都是同步的,因为字符串常量“123”只有一把锁。当m为静态成员变量原理也是一样的。

静态变量和字符串常量都是存放在方法区中的

当m为局部变量时
首先需要知道,局部变量随着函数每一次调用都是被重新创建。

public void withdraw(int m) {
		String a=new String();
		//如果是String s=“123”的s也是局部变量,但是其内存是在方法区中的,会共享同一块内存
		//所以会发生线程同步现象
		synchronized (a) {
			int before = accountmoney;
			int after = before - m;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			this.getmoney(after);
		}
	}
//执行结果
t2:取款金额为:5000 余额:5000
t1:取款金额为:5000 余额:5000

Java中的三大变量
实例变量:堆中(共享)
静态变量:方法区中(共享)
局部变量:栈中(不共享),永远不会发生线程安全问题。

扩大同步

public void run() {
		synchronized (account) {
			account.withdraw(5000);
			System.out.println(Thread.currentThread().getName() + ":取款金额为:" + 5000 + " 余额:" + account.getbalance());
		}
	}

synchronized用在实例方法上面
此时只能锁的是this,不灵活,同步的是整个方法,无故扩大同步范围,影响执行效率,优点:代码节俭
用法:

public synchronized void withdraw(int m) {
		String a=new String();
			int before = accountmoney;
			int after = before - m;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.getmoney(after);
	}

总结
1.synchronized只是对大括号里面的代码同步执行,其余代码正常执行。
2.锁更像是一个内存,当这个内存被一个线程占用了,那么其他线程就得排队。
3.StringBuffer,Vector,Hashtable都是线程安全的;StringBuilder,ArrayList,HashMap,HashSet都是线程不安全的。

线程安全的类

1.HashMap和HashTable都实现了Map接口,但是HashMap不是线程安全的类,HashTable是线程安全的类
2.StringBuffer是线程安全的类,适合在多线程情况下进行字符串拼接;StringBuilder不是线程安全的类,在单线程的时候,StringBuilder比StringBuffer要快
3.Vector是线程安全的,ArrayList不是线程安全的
4.可以通过Collection操作将非线程安全的类转换成线程安全的类
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = Collections.synchronizedList(list1);

线程池

当有很多个线程需要启动和结束的时候,会导致系统变慢!所以引入线程池的设计思想!
1.创建一个任务容器
2.一次性启动十个线程
3.这是个线程都处于wait状态
4.有一个“任务”被扔到线程中来,那么就有一个线程被唤醒
5.被唤醒的线程执行完这个“任务”之后,继续等待下一个“任务”
6.如果有多个“任务”,就有多个线程被唤醒
当任务被执行完毕后,线程并不会结束,继续循环已经存在的线程!

手写线程池代码:万物皆对象
1.创建一个ThreadPool的类,里面要有装线程的一个容器,容器里面的类是要继承Thread的;
2.容器里面的线程是干什么用的,用来处理接收到的任务,添加任务需要有一个add函数
3.线程在ThreadPool对象的时候就应该被启动好了,所以得有一个方法,用来启动线程
4.线程启动,代表里面run被执行,线程不会关闭,所以while(true),因为对于容器对象来说,添加任务和执行任务会产生安全问题
看代码:

package lockpool;

import java.util.LinkedList;

public class ThreadPool {  
	    // 线程池大小
	    int threadPoolSize;
	  
	    // 任务容器
	    LinkedList<Runnable> tasks = new LinkedList<Runnable>();
	  
	    // 试图消费任务的线程
	  
	    public ThreadPool() {
	        threadPoolSize = 10;
	  
	        // 启动10个任务消费者线程
	        synchronized (tasks) {
	            for (int i = 0; i < threadPoolSize; i++) {
	                new TaskConsumeThread("任务消费者线程 " + i).start();
	            }
	        }
	    }
	  
	    public void add(Runnable r) {
	        synchronized (tasks) {
	            tasks.add(r);
	            // 唤醒等待的任务消费者线程
	            tasks.notifyAll();
	        }
	    }
	  
	    class TaskConsumeThread extends Thread {
	        public TaskConsumeThread(String name) {
	            super(name);
	        }
	  
	        Runnable task;
	  
	        public void run() {
	            System.out.println("启动: " + this.getName());
	            while (true) {
	                synchronized (tasks) {
	                    while (tasks.isEmpty()) {
	                        try {
	                            tasks.wait();
	                        } catch (InterruptedException e) {
	                            // TODO Auto-generated catch block
	                            e.printStackTrace();
	                        }
	                    }
	                    task = tasks.removeLast();
	                    // 允许添加任务的线程可以继续添加任务
	                    tasks.notifyAll();
	  
	                }
	                System.out.println(this.getName() + " 获取到任务,并执行");
	                task.run();
	            }
	        }
	    }
}
public class TestA {

	public static void main(String[] args) {
		 ThreadPool pool = new ThreadPool();
		  
	        for (int i = 0; i < 5; i++) {
	            Runnable task = new Runnable() {
	                @Override
	                public void run() {
	                    //System.out.println("执行任务");
	                    //任务可能是打印一句话
	                    //可能是访问文件
	                    //可能是做排序
	                }
	            };             
	            pool.add(task);     
	            try {
	                Thread.sleep(1000);
	            } catch (InterruptedException e) {
	                // TODO Auto-generated catch block
	                e.printStackTrace();
	            }
	        }
	}

}

java自带的线程池
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
第一个参数: 10代表线程池的默认容量
第二个参数: 15代表最大容量,当任务超过10个之后,会把容量扩充至15
第三个参数: 结合第四个参数TimeUnit.SECONDS,表示经过60秒,多出来的线程还没有接到活儿,就会回收,最后保持池子里就10个
第五个参数: new LinkedBlockingQueue() 用来放任务的集合

 public static void main(String[] args) throws InterruptedException {           
        ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());           
        threadPool.execute(new Runnable(){   
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("任务1");
            }              
        });
    }

lock对象

lock是一个接口,声明一个接口类的对象:
Lock lock = new ReentrantLock();

占用锁:lock.lock();
一旦占用不会自动释放,需要自己手动释放:lock.unlock();(放在finally中进行);

Lock lock = new ReentrantLock();
 
        Thread t1 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("试图占有对象:lock");
 
                    lock.lock();
 
                    log("占有对象:lock");
                    log("进行5秒的业务操作");
                    Thread.sleep(5000);
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log("释放对象:lock");
                    lock.unlock();
                }
                log("线程结束");
            }
        };
        t1.setName("t1");
        t1.start();

trylock
trylock执行的就是在规定的时间内去抢占锁,所以存在没有占用成功的现象,所以在释放锁的时候需要一个标记来判断!

Lock lock = new ReentrantLock();
 
        Thread t1 = new Thread() {
            public void run() {
                boolean locked = false;
                try {
                    log("线程启动");
                    log("试图占有对象:lock");
 
                    locked = lock.tryLock(1,TimeUnit.SECONDS);
                    if(locked){
                        log("占有对象:lock");
                        log("进行5秒的业务操作");
                        Thread.sleep(5000);
                    }
                    else{
                        log("经过1秒钟的努力,还没有占有对象,放弃占有");
                    }
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                     
                    if(locked){
                        log("释放对象:lock");
                        lock.unlock();
                    }
                }
                log("线程结束");
            }
        };
        t1.setName("t1");
        t1.start();

lock对象的对象交互
通过调用lock对象的newCondition方法的返回一个Condition对象,通过调用这个对象的await,signal,signalAll方法去实现线程交互

public class Mylock {
	public static void main(String[] args) {
		Lock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				lock.lock();
				log("t1成功抢占锁");
				log("t1执行五秒钟的操作");
				log("t1执行暂停,并且释放锁");				
				try {
					condition.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}			
				// condition.signal();
				log("t1又开始执行");
			}
		});
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				lock.lock();
				log("t2成功抢占锁");
				log("t2执行五秒钟的操作");
				// condition.signal();
				log("t2执行完成");
				condition.signalAll();
				lock.unlock();//这一句必须加上,因为signal只是唤醒,并没有释放锁
			}
		});
		t2.start();
	}
	public static void log(String str) {
		System.out.println(new Date() + str);
	}
}

原子访问

原子访问是指一条语句执行不可拆分,不可中断的操作!比如赋值语句·int a=5
但是i++不是原子语句,分为三个原子性操作在一起的,1.取i的值;2.i加1;3.赋值给i;所以这就有可能会产生线程安全问题!
java包里有一个把一些操作封装成了原子性操作!java.util.concurrent.atomic

public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicI =new AtomicInteger();
        int i = atomicI.decrementAndGet();
        int j = atomicI.incrementAndGet();
        int k = atomicI.addAndGet(3);         
}

同步测试:分别使用基本变量的非原子性的++运算符和 原子性的AtomicInteger对象的 incrementAndGet 来进行多线程测试。

package mythread;

import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class Mylock {
	private static int values = 0;
	private static AtomicInteger atomicValue = new AtomicInteger();
	public static void main(String[] args) {
		Thread[] ts1 = new Thread[100000];
		for (int i = 0; i < 100000; i++) {
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					values++;
				}
			});
			t.start();
			ts1[i] = t;
		}
		// 等待线程结束
		for (int i = 0; i < 100000; i++) {
			try {
				ts1[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(values);
		Thread[] ts2 = new Thread[100000];
		for (int i = 0; i < 100000; i++) {
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					atomicValue.incrementAndGet();
				}
			});
			t.start();
			ts2[i] = t;
		}
		// 等待线程结束
		for (int i = 0; i < 100000; i++) {
			try {
				ts2[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(atomicValue.intValue());
	}
}

死锁

死锁现象的产生是由于共享对象被两个线程占用没有得到释放,程序无法执行下一步,运行不会出错,会永远僵持在那里。synchronized的嵌套使用比较容易造成这种现象。

package threadsafe;

public class Threadsafe1 {

	public static void main(String[] args) {
		
		Object o1=new Object();
		Object o2=new Object();
		MyThreada m1=new MyThreada(o1,o2);
		MyThreadb m2=new MyThreadb(o1,o2);
		m1.setName("m1");
		m2.setName("m2");
		m1.start();
		m2.start();
		
	}
}

class MyThreada extends Thread{
	private Object o1;
	private Object o2;	
	public MyThreada(Object o1,Object o2) {
		this.o1=o1;
		this.o2=o2;
	}

	@Override
	public void run() {
		synchronized(o1) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized(o2) {
				System.out.println(Thread.currentThread().getName());
			}
		}		
	}		
}
class MyThreadb extends Thread{
	private Object o1;
	private Object o2;	
	public MyThreadb(Object o1,Object o2) {
		this.o1=o1;
		this.o2=o2;
	}

	@Override
	public void run() {
		synchronized(o2) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized(o1) {
				System.out.println(Thread.currentThread().getName());
			}
		}		
	}		
}

开发中如何解决线程安全问题

1.尽量用局部变量代替实例变量和静态变量
2.如果必须是实例变量,考虑尽量多创建几个对象
3.用synchronized

守护线程

Java语言中的线程分为两大类,一类是用户线程,一类是守护线程,例如:main线程就是用户线程,垃圾回收线程就是守护线程。
守护线程的特点:一般是死循环,随着用户线程的结束而结束;用来每隔一段时间做某一件事,这也可以设置一个定时器,并且将定时器设置为守护线程
实现守护线程
语法:线程.setDaemon(true);

public class Threadsafe3 {
	public static void main(String[] args) {
		BakThread b=new BakThread();
		b.setName("备份线程");
		b.setDaemon(true);
		b.start();		
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"-->"+i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
}

class BakThread extends Thread{
	 @Override
	public void run() {
		int i=0;
		while(true) {
			System.out.println(Thread.currentThread().getName()+"--->"+i++);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}			
		}
	}	
}
//执行结果
main-->0
守护线程--->0
守护线程--->1
main-->1
main-->2
守护线程--->2
守护线程--->3
main-->3
守护线程--->4
main-->4
守护线程--->5
main-->5
守护线程--->6
main-->6
main-->7
守护线程--->7
main-->8
守护线程--->8
main-->9
守护线程--->9
守护线程--->10

定时器

作用:间隔特定的时间去执行特定的程序
三种实现方法:
1.设置线程睡眠时间(最原始)
2.java.util.Timer是封装好的定时器,可以直接用,也很少用,因为很多框架自带定时器
3.Spring框架中SpringTask里面有定时器

实现定时器

Timmer t=new Timer();
t.schedule(TimerTask task,起始时间,间隔时间);
//TimerTask implements Runnable
//所以任务代码可以是Runna类
public class Threadsafe4 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Timer t=new Timer(true);//true是把定时器设置为守护线程,默认是false
		SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
		try {
			Date firsttime=sdf.parse("2020-10-11 05:48:00");
			t.schedule(new MyThreadsafe4(),firsttime,1000);
			//也可以用匿名类的形式
			//new TimerTask(){重写run方法};
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}		
	}
}

class MyThreadsafe4 extends TimerTask{
	public void run() {
		SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
		String s=sdf.format(new Date());
		System.out.println(s+"数据保存了");
	}
}
//执行结果
main--->0
2020-10-11 15:59:59数据保存了
main--->1
2020-10-11 16:00:00数据保存了
main--->2
2020-10-11 16:00:01数据保存了
main--->3
2020-10-11 16:00:02数据保存了
main--->4
2020-10-11 16:00:03数据保存了
main--->5
2020-10-11 16:00:04数据保存了
main--->6
2020-10-11 16:00:05数据保存了
main--->7
2020-10-11 16:00:06数据保存了
main--->8
2020-10-11 16:00:07数据保存了
main--->9
2020-10-11 16:00:08数据保存了
2020-10-11 16:00:09数据保存了

实现线程的第三种方式:FutureTask,实现Callable接口(JDK8新特性)

这种方式实现的线程得到线程完成之后的返回值。用这种方式可以拿到实现结果
语法:
FutureTask task=new FutureTask(Callable接口的实现类对象); Thread t=new Thread(task);t.start();Object o=task.get()

public class Threadsafe5 {
	public static void main(String[] args) {
		@SuppressWarnings("unchecked")
		FutureTask  task=new FutureTask(new Callable() {
			@Override
			public Object call() throws Exception {
				System.out.println(Thread.currentThread().getName()+"--->begain");
				Thread.sleep(1000);		
				System.out.println(Thread.currentThread().getName()+"--->end");
				int a=5;
				int b=10;
				return a+b;//自动装箱
			}			
		});
		
		Thread t=new Thread(task);
		t.setName("未来线程");
		t.start();
		try {
			Object o=task.get();//会阻塞主线程
			System.out.println(o);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
}
//执行结果
未来线程--->begain
未来线程--->end
15

缺点: FutureTask.get()会导致当前线程阻塞,效率比较低。

wait() 和notify()

这两个方法是Object 的方法,所以所有类都有这个方法。
Object o=new Obejct();o.wait()
wait()方法让执行o上的线程进入无期限等待状态。
执行o上的线程就是上面代码块所在的线程。
o.notify()是随机唤醒一个执行o上面的线程。
notifyall()唤醒在此对象上的所有等待的线程

wait()和notify()方法是建立在线程同步的基础上的。o.wait()方法会让当前线程进入等待状态,并让当前线程释放之前占用o的对象锁;notify()方法只会通知(可以执行了),不会释放之前占用o的对象锁

生产者和消费者模式

这里面用trylock去实现生产者和消费者模式!

问题

什么时候开始抢夺时间片呢?

start()之后,run()之前。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值