多线程 Thread 线程同步 synchronized

Posted hello4world

tags:

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

1.多线程基础以及两种启用方式

/**
 * 多线程
 * 多线程改变了代码的执行方式,从原有的所有代码都串行操作改变为多个代码片段之间并行操作。
 * 因此多线程允许多个代码片段"同时运行"。
 * 
 * 创建线程的方式有两种
 * 1:继承线程并重写run方法,在run方法中定义线程要执行的任务。
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        /*
         * 启动线程是调用线程的start方法而不是直接调用run方法。
         * 当线程的start方法调用后,线程纳入到线程调度中,当其第一次分配到时间片开
         * 始运行时,它的run方法会自动被执行。
         */
        t1.start();
        t2.start();
    }
}
/**
 * 第一种创建线程的方式优点是创建简单方便
 * 但是缺点也比较明显:
 * 1:由于java是单继承的,这导致继承了线程就无法再继承其他的类,这会导致无法重用其他
 *   超类的方法而产生继承冲突问题。
 * 2:定义线程的同时重写run方法,这就等于规定了线程要执行的具体任务,导致线程与其执行的
 *   任务产生必然的耦合关系,不利于线程的重用  
 */
class MyThread1 extends Thread{
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println("你愁啥?");
        }
    }
} 
class MyThread2 extends Thread{
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println("瞅你咋地!");
        }
    }
} 
/**
 * 第二种创建线程的方式
 * 实现Runnable接口并重写run方法来单独定义线程的任务。
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        //单独实例化任务
        Runnable r1 = new MyRunnable1();
        Runnable r2 = new MyRunnable2();        
        //创建线程
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);    
        t1.start();
        t2.start();
    }
}

class MyRunnable1 implements Runnable{
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println("你愁啥?");
        }
    }
}
class MyRunnable2 implements Runnable{
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println("瞅你咋地!");
        }
    }
}

2.使用匿名内部类方式创建多线程

/**
 * 使用匿名内部类的创建形式完成线程的两种创建
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        //第一种创建方式
        Thread t1 = new Thread() {
            public void run() {
                for(int i=0;i<1000;i++) {
                    System.out.println("你是谁啊?");
                }
            }
        };    
        //第二种创建方式
        Runnable r2 = new Runnable() {
            public void run() {
                for(int i=0;i<1000;i++) {
                    System.out.println("我是查水表的!");
                }
            }
        };
        Thread t2 = new Thread(r2);        
        t1.start();
        t2.start();        
    }
}

3.获取线程信息的相关方法

/**
 * 获取线程信息的相关方法
 */
public class ThreadInfoDemo {
    public static void main(String[] args) {
        //获取主线程
        Thread main = Thread.currentThread();
        //获取线程的名字
        String name = main.getName();
        System.out.println("name:"+name);
        //获取唯一标识
        long id = main.getId();
        System.out.println("id:"+id);
        //获取线程的优先级
        int priority = main.getPriority();
        System.out.println("优先级:"+priority);        
        boolean isAlive =  main.isAlive();
        boolean isDaemon = main.isDaemon();
        boolean inInterrupted = main.isInterrupted();
        System.out.println("isAlive:"+isAlive);
        System.out.println("isDaemon:"+isDaemon);
        System.out.println("inInterrupted:"+inInterrupted);        
    }
}
/**
 * 线程提供了一个静态方法:
 * static Thread currentThread()
 * 该方法可以获取运行这个方法的线程
 * 
 * 实际上java的所有代码都是靠线程运行的,main方法也不例外。运行main方法的线程不是由我们
 * 创建的,而是JVM自行创建的,并用来运行main方法。而我们通常称这个线程为"主线程"
 */
public class CurrentThreadDemo {
    public static void main(String[] args) {
        Thread main = Thread.currentThread();
        System.out.println("运行main方法的线程是:"+main);
        dosome();        
        Thread t = new Thread() {
            public void run() {
                Thread t = Thread.currentThread();
                System.out.println("自定义线程:"+t);
                dosome();
            }
        };
        t.start();
    }
    
    public static void dosome() {
        Thread t = Thread.currentThread();
        System.out.println("运行dosome方法的线程是:"+t);
    }
}

4.线程优先级

/**
 * 线程的优先级
 * 线程启动后就纳入到了线程调度中统一管理,什么时候获取CPU时间片完全取决于线程调度,
 * 线程是不能主动索取的,通过调整线程的优先级可以最大程度的干涉分配CPU时间片的几率。
 * 理论上线程优先级越高的线程获取CPU时间片的几率越高。
 * 
 * 线程的优先级有10个等级,用整数1-10表示,1是最小,5是默认,10是最高
 */
public class PriorityDemo {
    public static void main(String[] args) {
        Thread min = new Thread() {
            public void run() {
                for(int i=0;i<10000;i++) {
                    System.out.println("min");
                }
            }
        };
        Thread norm = new Thread() {
            public void run() {
                for(int i=0;i<10000;i++) {
                    System.out.println("nor");
                }
            }
        };
        Thread max = new Thread() {
            public void run() {
                for(int i=0;i<10000;i++) {
                    System.out.println("max");
                }
            }
        };    
        min.setPriority(Thread.MIN_PRIORITY);
        max.setPriority(Thread.MAX_PRIORITY);    
        min.start();
        norm.start();
        max.start();
    }
}

5.sleep方法

/**
 * 线程提供了一个静态方法:
 * static void sleep(long ms)
 * 该方法可以让运行这个方法的线程处于阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE
 * 状态再次并发运行。
 */
public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("程序开始了");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("程序结束了");
    }
}
/**
 * sleep方法要求处理中断异常
 * 当一个线程调用sleep方法处于阻塞状态的过程中,此时被其他线程调用了该线程的interrupt
 * 方法,那么就会打断这个线程的睡眠阻塞,此时sleep方法就会抛出中断异常告知。
 */
public class SleepDemo2 {
    public static void main(String[] args) {
        Thread lin = new Thread() {
            public void run() {
                System.out.println("林:刚美完容,睡一会...");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
                }
                System.out.println("林:醒了!");
            }
        };    
        Thread huang = new Thread() {
            public void run() {
                System.out.println("黄:开始砸墙!");
                for(int i=0;i<5;i++) {
                    System.out.println("黄:80!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("咣当!");
                System.out.println("黄:搞定!");
                //中断lin的阻塞状态
                lin.interrupt();
            }
        };    
        lin.start();
        huang.start();
    }
}

6.线程之间的同步方法join

/**
 * join方法允许当前线程在join方法所属线程上等待,直到该线程结束后结束join阻塞继续后续
 * 操作。所以join方法可以协调线程的同步运行。
 * 
 * 同步运行:多个线程之间执行有顺序。
 * 异步运行:多个线程之间各自执行各自的。
 */
public class JoinDemo {
    private static boolean isFinish = false;
    public static void main(String[] args) {
        /*
         * 当一个方法的局部内部类中引用了这个方法的其他局部变量时,该变量必须声明为final的。
         * JDK8之后可以不写final.但是该变量依然会被编译器最终改为final的。
         * 这源自JVM的内存分配问题。
         */
//        boolean isFinish = false;    
        Thread download = new Thread() {
            public void run() {
                System.out.println("down:开始下载图片!");
                for(int i=1;i<=100;i++) {
                    System.out.println("down:"+i+"%");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("down:图片下载完毕!");
                isFinish = true;
            }
        };    
        Thread show = new Thread() {
            public void run() {
                try {
                    System.out.println("show:开始显示文字...");
                    Thread.sleep(3000);
                    System.out.println("show:文字显示完毕!");                    
                    System.out.println("show:开始显示图片...");
                    /*
                     * 先等待download线程执行完毕(图片下载完)之后在继续后续工作
                     */
                    download.join();                
                    if(!isFinish) {
                        throw new RuntimeException("图片加载失败!");
                    }
                    System.out.println("show:显示图片完毕!");                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };        
        download.start();
        show.start();
    }
}

7.守护线程(后台线程)daemon

/**
 * 守护线程
 * 守护线程也称为后台线程,创建和使用上与前台线程一样,但是有一点不同:
 * 当进程结束时,所有正在运行的守护线程都会被强制停止。
 * 
 * 进程的结束:当所有普通线程都结束时,进程结束
 */
public class DaemonThreadDemo {
    public static void main(String[] args) {
        Thread rose = new Thread() {
            public void run() {
                for(int i=0;i<5;i++) {
                    System.out.println("rose:let me go!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("rose:啊啊啊啊啊啊AAAAAAaaaaa....");
                System.out.println("噗通!");
            }    
        };
        
        Thread jack = new Thread() {
            public void run() {
                while(true) {
                    System.out.println("jack:you jump!i jump!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };    
        rose.start();
        /*
         * 设置为守护线程要在线程启动前进行
         */
        jack.setDaemon(true);
        jack.start();        
    }
}

8.线程同步

/**
 * 多线程并发的安全问题
 * 当多个线程并发访问同一临界资源,由于线程切换时机不确定,导致多个线程操作该资源未
 * 按照程序设计的顺序进行,导致出现错误,严重时可能出现系统瘫痪等情况。
 * 
 * 临界资源:同一时间只能被一条线程操作的资源
 */
public class SyncDemo {
    public static void main(String[] args) {
        Table table = new Table();    
        Thread t1 = new Thread() {
            public void run() {
                while(true) {
                    int bean = table.getBean();
                    Thread.yield();//模拟CPU没有时间了
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    int bean = table.getBean();
                    Thread.yield();//模拟CPU没有时间了
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

class Table{
    //桌子上有20个豆子
    private int beans = 20;
    /**
     * 当一个方法使用关键字synchronized修饰后这个方法称为"同步方法",多个线程不能同时
     * 在方法内部执行。
     * 将多个线程异步操作临界资源改为同步操作就可以解决多线程的并发安全问题了。
     */
    public synchronized int getBean() {
        if(beans==0) {
            throw new RuntimeException("没有豆子了!");
        }
        /*
         * static void yield()
         * 当一个线程执行到这个方法时会主动让出本次CPU时间片并回到RUNNABLE状态。
         */
        Thread.yield();//模拟CPU没有时间了
        return beans--;
    }
}
/**
 * 同步块
 * 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发的效率
 * 
 * synchronized(同步监视器对象){
 *     需要多线程同步运行的代码片段
 * }
 * 同步块可以更准确的锁定需要同步运行的代码片段,从而有效控制同步范围。
 */
public class SyncDemo2 {
    public static void main(String[] args) {
        Shop shop = new Shop();    
        Thread t1 = new Thread() {
            public void run() {
                shop.buy();
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                shop.buy();
            }
        };
        t1.start();
        t2.start();
    }
}

class Shop{
    /*
     * 若在方法上直接使用synchronized,那么同步监视器对象就是当前方法所属对象this
     */
//    public synchronized void buy() {
    public void buy() {
        try {
            Thread t = Thread.currentThread();        
            System.out.println(t.getName()+":正在挑衣服...");
            Thread.sleep(5000);
            /*
             * 使用同步块时要注意,多个需要同步运行该代码片段的线程看到的同步监视器对象,即
             * 上锁的对象必须是同一个才可以。否则没有同步效果!

             * 下面注释的代码就是个错误的用法,当多个线程看到的同步监视器对象不是同一个时,
             * 是没有同步效果的。
             */
//            synchronized (new Object()) {
            synchronized (this) {
                System.out.println(t.getName()+":正在试衣服...");
                Thread.sleep(5000);
            }        
            System.out.println(t.getName()+":结账离开!");            
        } catch (Exception e) {
        }
    }
}
/**
 * 静态方法若使用synchronized修饰后,那么该方法一定具有同步效果。
 * 
 * 静态方法的同步监视器对象使用的是当前类的类对象(Class的实例)
 * 类对象后面反射的课程中会介绍。
 */
public class SyncDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
                Boo.dosome();
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                Boo.dosome();
            }
        };
        t1.start();
        t2.start();
    }
}

class Boo{
//    public synchronized static void dosome() {
    public static void dosome() {
        synchronized (Boo.class) {
            Thread t = Thread.currentThread();
            System.out.println(t.getName()+":正在执行dosome方法...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
            }
            System.out.println(t.getName()+":运行dosome方法完毕!");    
        }    
    }
}
/**
 * 互斥锁
 * 当使用synchronized锁定多个代码片段,并且指定的同步监视器对象是同一个时,这些代码片
 * 段间就是互斥的,多个线程不能同时在这些代码片段间一起执行。
 */
public class SyncDemo4 {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Thread t1 = new Thread() {
            public void run() {
                foo.methodA();
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                foo.methodB();
            }
        };
        t1.start();
        t2.start();
    }
}

class Foo{
    public synchronized void methodA() {
        Thread t = Thread.currentThread();
        System.out.println(t.getName()+":正在执行A方法...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println(t.getName()+":执行A方法完毕!");
    }
    public synchronized void methodB() {
        Thread t = Thread.currentThread();
        System.out.println(t.getName()+":正在执行B方法...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println(t.getName()+":执行B方法完毕!");
    }
}

9.倒计时demo

public class Test {
    public static void main(String[] args) {
        /*
         * 倒计时程序
         * 程序启动后,要求在控制台输入一个数然后从这个数开始每秒递减并输出该数
         * 到0时输出时间到,程序结束。
         */
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入一个数字:");
        int num = Integer.parseInt(scanner.nextLine());        
        for(;num>0;num--) {
            System.out.println(num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("时间到!");    
    }
}

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

多线程 Thread 线程同步 synchronized

初学Java多线程:使用Synchronized块同步方法

Thread实现多线程死锁同步锁

多线程Thread线程创建

c++ thread创建与多线程同步详解

浅谈Java三种实现线程同步的方法