守护线程和用户线程
- 守护线程和用户线程基本上相同,唯一的区别就是判断JVM何时离开
- 守护线程是用来服务用户线程的。在start方法之前调用thread.setDaemon(true)可以将一个用户线程变成守护线程
- java的垃圾回收,他是一个典型的守护线程
- 如果JVM中都是守护线程,JVM将退出(用户线程执行结束,守护线程无论是否结束,都将终止执行)。
线程的状态
线程的状态,分别有:
新建:当一个Thread类及其子类的对象被声明并创建时,此时线程就处于新建状态(还未启动)
就绪:处于新建状态的线程被start后,线程将进入CPU的执行队列等待获取CPU的执行权,此时
线程具备了运行的条件,但还未获得CPU的执行权
运行:当就绪的线程获得CPU的执行权
阻塞:在某种特殊的情况下,被人为地挂起或执行输入输出操作时,让出CPU并临时终止自己的执行
死亡:线程完成了他的全部工作或被提前强制终止或出现异常导致线程异常结束
注意:一个线程一旦死亡,是不可以再重新启动的
jdk中Thread State定义了线程的状态,有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED六种
线程同步
出现数据安全问题的条件:
1、多线程环境
2、有共享数据
3、多条语句操作共享数据
如何解决线程安全问题:
基本思想:让程序没有安全问题的环境
具体实现:把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个线程执行
Java提供的解决方法是使用同步代码块或同步方法:synchronized 相当于给代码加锁
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断
//同步代码块
public class SellTicket implements Runnable{
private Object obj = new Object();
@Override
public void run() {
while(true){
// 给以下代码加锁 括号中需要一个索对象(任何对象都可以充当锁对象,一般情况下使用this)
synchronized (obj){
//语句
}
}
}
//非静态同步方法,这时的锁对象是this
public class SellTicket implements Runnable{
@Override
public void run() {
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){//这才是真正存在数据安全的操作 这个就称为同步方法 这里锁对象时this
//语句
}
}
//静态同步方法和静态方法中的同步代码块,锁对象是类名.class
public class SellTicket implements Runnable{
private static int ticktes = 100;
// private String s = "abc";
@Override
public void run() {
while(true){
sellTicket();
}
}
public static synchronized void sellTicket(){
//语句
}
}
public static void sellTicket(){
synchronized(SellTicket.class){ //语句 }
}
单例设计模式的懒汉式的线程安全问题
当并发访问单例中提供的获取对象方法时,得到的可能不是同个对象
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if( instance == null){ //可以提高效率
synchronized (Singleton.class){
if(instance ==null){
instance = new Singleton();
}
}
}
return instance;
}
}
线程安全的类
有StringBuffer,Vector,HashTable等这几种。
实际使用时,如果不需要线程安全的实现,推荐使用与之功能相同但线程不同步的实现
线程的死锁
不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就行成了线程死锁
出现死锁 不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态 无法继续
死锁问题的出现是一个概率事件
死锁问题的解决:
- 减少同步代码块或同步方法的嵌套
- 减少同步资源的定义
- 使用专门的算法
public class DeadLockDemo {
public static void main(String[] args) {
final StringBuilder s1 = new StringBuilder();
final StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s2.append("A");
synchronized (s2){
s2.append("B");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (s2){
s2.append("C");
synchronized (s1){
s1.append("D");
System.out.println(s2);
System.out.println(s1);
}
}
}
}.start();
}
}
线程的通信
多个线程并发执行,在默认情况下CPU随机切换线程,如果我们希望他们有规律的执行,就需要使用线程通信。
如果线程需要等待 就调用的wait().如果要唤醒一个等待的线程 那么就使用notify() /notifyAll()
如果想让三个线程按照顺序依次执行 此时wait和notifyAll做不到
public class ThreadWait {
public static void main(String[] args) {
ThreadWait tw = new ThreadWait();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
tw.print1();
}
}
}).start();
new Thread(){
@Override
public void run() {
while(true){
tw.print2() ;
}
}
}.start();
}
public synchronized void print1(){
notify();
System.out.print("中");
System.out.print("北");
System.out.print("大");
System.out.print("学");
System.out.println();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void print2(){
notify();
System.out.print("塔");
System.out.print("里");
System.out.print("木");
System.out.print("大");
System.out.print("学");
System.out.println();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
更详细的说明
sleep()和wait()的区别:
sleep和yield是Thread的方法,wait和notify是object方法。
Synchronized关键字可以和对象的机锁交互,来实现线程的同步。
sleep方法无法给i便对象的机锁,wait方法会在线程休眠的同时释放掉机锁,其它线程可以访问该对象。
yield方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么yield方法不会起作用
一个机锁被释放的标志是:synchronized块或方法结束。
join使当前线程停下来等待,直至另一个调用join方法的线程终止
互斥锁
互斥锁 依次最多只能有一个线程持有锁
锁是用于通过多个线程控制对共享资源的访问的工具,通常,锁提供对共享资源的独占访问
Lock 接口可以使用的实现类ReetrantLock:
一个可重入互斥具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能
Condition类
await()
await(long time, TimeUnit unit) //使当前线程等待直到发出信号或中断,或指定的等待时间过去
signal() //唤醒一个等待线程
signalAll() //唤醒所有等待线程
//不同的线程需要使用不同的 Condition 这样就能区分唤醒额时候唤醒的是那个线程
Lock锁使用实例:
// 创建Lock锁对象
Lock lock = new ReentrantLock();
//创建锁使用的条件
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
// 创建一个唤醒标志
int flag = 1;
public void print1(){
lock.lock();//给当前代码上锁
if(flag != 1){
try {
c1.await();//当前线程处于等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
语句1
flag = 2;
c2.signal();//唤醒c2
lock.unlock();//释放锁
}
}