一、基础篇:
1、线程的定义
线程(thread)是操作系统进程中能够独立执行的实体(控制流),是处理器调度和分派的基本单位。
2、线程的属性
并发性,共享性,动态性,结构性
3、线程的状态
4、线程的调度
★主要是通过实现Runnable接口和继承Thread类来实现线程的调度和操作
a、Runnable接口(里面就一个run方法,只要通过重写run方法就可以实现自己想要的线程功能)
[java] view
plain copy
- public interface Runnable
- {
- public abstract void run();
- }
b、Thread线程类(继承这个类)
[java] view
plain copy
- public class Thread extends Object implements Runnable
- {
- public Thread() //构造方法
- public Thread(String name) //name指定线程名
- public Thread(Runnable target) //target指定线程的目标对象
- public Thread(Runnable target, String name)
- public void run() //描述线程操作的线程体
- public final String getName() //返回线程名
- public final void setName(String name) //设置线程名
- public static int activeCount() //返回当前活动线程个数
- public static Thread currentThread() //返回当前执行线程对象
- public Sting toString() //返回线程的字符串信息
- public void start() //启动已创建的线程对象
- }
★两种创建线程方式的比较
(1) 继承线程Thread类
public class NumberThread extends Thread
(2) 实现Runnable接口
public class NumberRunnable implements Runnable
1) 声明继承Thread类的奇数/偶数序列线程
a) main是首先启动执行的线程
b) 两个线程交替运行
[java] view
plain copy
- package thread.hello;
- public class MyThread extends Thread{
- private int num=0;
- public MyThread(int num) {
- this.num = num;
- }
- @Override
- public void run() {
- for (int i=num;i<=100;i+=2){
- System.out.print(i+" ");
- }
- System.out.println();
- }
- public static void main(String[] args) {
- Thread t1=new MyThread(1);
- Thread t2=new MyThread(2);
- t1.start();
- t2.start();
- }
- }
2) 声明实现Runnable接口的奇数/偶数序列线程
[java] view
plain copy
- package thread.hello;
- public class MyThread2 {
- public static void main(String[] args) {
- MyRun r1=new MyRun(1);
- MyRun r2=new MyRun(2);
- Thread t1=new Thread(r1);
- t1.start();
- Thread t2=new Thread(r2);
- t2.start();
- }
- }
- class MyRun implements Runnable {
- private int num=0;
- public MyRun(int num) {
- this.num = num;
- }
- @Override
- public void run() {
- for (int i=num;i<=100;i+=2){
- System.out.print(i+" ");
- }
- System.out.println();
- }
- }
★线程对象的优先级
1) Thread类中声明了3个表示优先级的公有静态常量:
[java] view
plain copy
- public static final int MIN__PRIORITY=1 //最低优先级
- public static final int MAX_PRIORITY=10 //最高优先级
- public static final int NORM_PRIORITY=5 //默认优先级
2) Thread类中与线程优先级有关的方法有以下2个:
[java] view
plain copy
- public final int getPriority() //获得线程优先级
- public final void setPriority(int newPriority)//设置线程优先级
★线程对象的生命周期
Thread.State类声明的线程状态,新建态、运行态、阻塞态和等待态、终止态
★Thread类中改变和判断线程状态的方法
1) 线程启动
[java] view
plain copy
- public void start() //启动线程对象
- public final boolean isAlive() //是否活动状态
2) 线程睡眠
[java] view
plain copy
- public static void sleep(long millis) throws InterruptedException
3) 线程中断
[java] view
plain copy
- public void interrupt() //设置中断标记
- public boolean isInterrupted() //判断是否中断
例子(滚动字):
[java] view
plain copy
- package MyThread2;
- import java.awt.FlowLayout;
- import java.awt.GridLayout;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JOptionPane;
- import javax.swing.JPanel;
- import javax.swing.JTextField;
- public class WelcomeJFrame extends JFrame {
- public WelcomeJFrame(String[] texts){
- super("Rolling words");
- this.setBounds(500, 500, 400, 300);
- this.setDefaultCloseOperation(EXIT_ON_CLOSE);
- if (texts==null||texts.length==0) {
- this.getContentPane().add(new RollbyJPanel("Welcome"));
- }else {
- this.getContentPane().setLayout(new GridLayout(texts.length,1));
- for (int i = 0; i < texts.length; i++) {
- this.getContentPane().add(new RollbyJPanel(texts[i]));
- }
- }
- this.setVisible(true);
- }
- // public WelcomeJFrame(){
- // this(null);
- // }
- public static void main(String args[]){
- String texts[]={"Hello","welcome","to","China"};
- new WelcomeJFrame(texts);
- }
- }
- class RollbyJPanel extends JPanel implements ActionListener,Runnable{
- private JTextField text_word,text_sleep,text_state;
- private JButton button_start,button_interrupt;
- private Thread thread_rollby;
- private int sleeptime;
- public RollbyJPanel(String str){
- this.setLayout(new GridLayout(2, 1));
- char space[]=new char[100];
- java.util.Arrays.fill(space,‘ ‘);
- text_word=new JTextField(str+new String(space));
- this.add(text_word);
- JPanel panel_sub=new JPanel();
- panel_sub.setLayout(new FlowLayout(FlowLayout.LEFT));
- this.add(panel_sub);
- panel_sub.add(new JLabel("sleep"));
- sleeptime=(int)(Math.random()*100);
- text_sleep=new JTextField(""+sleeptime);
- text_sleep.addActionListener(this);
- panel_sub.add(text_sleep);
- button_start=new JButton("start");
- button_start.addActionListener(this);
- panel_sub.add(button_start);
- button_interrupt=new JButton("interrupt");
- button_interrupt.addActionListener(this);
- panel_sub.add(button_interrupt);
- thread_rollby=new Thread(this);
- panel_sub.add(new JLabel("state"));
- text_state=new JTextField(""+thread_rollby.getState(),10);
- text_state.setEditable(false);
- panel_sub.add(text_state);
- }
- @Override
- public void actionPerformed(ActionEvent e) {
- if (e.getSource()==text_sleep) {
- try {
- sleeptime=Integer.parseInt(text_sleep.getText());
- } catch (NumberFormatException e1) {
- JOptionPane.showMessageDialog(this, "\""+text_sleep.getText()+"\""+"cannot be changed into an integer number");
- }
- }
- if (e.getSource()==button_start) {
- try {
- sleeptime=Integer.parseInt(text_sleep.getText());
- } catch (NumberFormatException e1) {
- JOptionPane.showMessageDialog(this, "\""+text_sleep.getText()+"\""+"cannot be changed into an integer number");
- }
- thread_rollby=new Thread(this);
- thread_rollby.start();
- text_state.setText(""+thread_rollby.getState());
- button_start.setEnabled(false);
- button_interrupt.setEnabled(true);
- }
- if (e.getSource()==button_interrupt) {
- thread_rollby.interrupt();
- text_state.setText(""+thread_rollby.getState());
- button_start.setEnabled(true);
- button_interrupt.setEnabled(false);
- }
- }
- @Override
- public void run() {
- while (true) {
- try {
- String str = text_word.getText();
- str = str.substring(1) + str.charAt(0);
- text_word.setText(str);
- Thread.sleep(sleeptime);
- } catch (InterruptedException e) {
- break;
- }
- }
- }
- }
结果界面:
二、交互线程:
1、运行结果不惟一,取决于线程调度
2、线程执行被打断时出现错误
3、线程互斥和临界区管理:操作系统对共享一个变量的若干线程进入各自临界区有以下3个调度原则:
1) 一次至多一个线程能够在它的临界区内。
2) 不能让一个线程无限地留在它的临界区内。
3) 不能强迫一个线程无限地等待进入它的临界区。特别地,进入临界区的任一线程不能妨碍正等待进入的其他线程的进展。
4、Java的线程互斥实现:
1) 同步语句
synchronized (对象)
语句
2) 同步方法
synchronized 方法声明
5、互斥的存/取款线程设计
Account:
[java] view
plain copy
- package thread3.bank3;
- public class Account {
- private String name; //储户姓名
- private double balance; //账户余额
- //开户
- public Account(String name){
- this.name = name;
- this.balance=0;
- }
- public String getName(){
- return name;
- }
- //查看余额
- public double balance(){
- return balance;
- }
- //存款
- public void put( double value){
- if(value>0){
- this.balance +=value;
- }
- }
- //取款: 如果余额不够就把剩余金额全部取出,如果余额足够则可取value
- public double get( double value){
- if(value>0){
- if(value <=balance){//存款够取
- this.balance -=value;
- }else{
- value = this.balance;
- this.balance = 0;
- }
- return value;
- }
- return 0;
- }
- synchronized public void fetchWork(double value){
- // 先看看有多少钱
- double howmuch = balance;
- // 模拟用户的操作时间差
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 取款
- double v = get(value);
- // 再看看还剩多少
- double remain = balance;
- // 信息输出
- System.out.println(name + "账户:现有" + howmuch + ",取出"
- + v + ",余额" + remain);
- }
- synchronized public void saveWork(double value){
- // 先看看账户中有多少钱
- double howmuch = balance;
- // 模拟用户的操作时间差
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 存钱
- put(value);
- // 再看看账户余额,以确认钱已经存进去了
- double b = balance;
- // 信息输出
- System.out.println(name+ "账户:现有" + howmuch + ",存入"
- + value + ",余额" + b);
- }
- }
- class Fetch extends Thread {
- private double value;// 取款金额
- private Account account;// 取款账户
- public Fetch(Account account, double value) {
- this.account = account;
- this.value = value;
- }
- @Override
- public void run() {
- this.account.fetchWork( this.value );
- }
- }
- class Save extends Thread {
- private double value;// 存款金额
- private Account account;// 存款账户
- public Save(Account account, double value) {
- this.account = account;
- this.value = value;
- }
- public void run() {
- account.saveWork(value);
- }
- }
Bank:
[java] view
plain copy
- package thread3.bank3;
- public class Bank {
- public static void main(String[] args) {
- //用同步方法: synchrozized public void aa() { ...... } //方法aa()的调用对象就是对象锁
- Account li = new Account("Li");
- Account wang = new Account("Wang");
- Save s1 = new Save(wang,100);
- Save s2 = new Save(wang,200);
- Save s5 = new Save(wang,300);
- Save s4 = new Save(wang,200);
- Fetch f1 = new Fetch(wang,300);
- Save s3 = new Save(li,100);
- s3.start();
- s2.start();
- s1.start();
- s4.start();
- s5.start();
- f1.start();
- }
- }
正确结果:
错误结果:
三、加强篇:
1、线程互斥锁
a、多线程互斥共享“基本数据类型数据”资源,锁(用synchronized关键字)的必须是对象,基本数据类型的变量不能当作对象锁,同时,要保证多线程使用的是同一个互斥锁(对象锁),才能进行同步。
b、多线程互斥共享“栈”资源
举例:多窗口买票
[java] view
plain copy
- package thread.ticket.v1;
- public class SellingTickets {
- public static void main(String[] args) {
- Window r1=new Window("窗口1");
- Thread t1=new Thread(r1);
- t1.start();
- Window r2=new Window("窗口2");
- Thread t2=new Thread(r2);
- t2.start();
- Window r3=new Window("窗口3");
- Thread t3=new Thread(r3);
- t3.start();
- Window r4=new Window("窗口4");
- Thread t4=new Thread(r4);
- t4.start();
- }
- }
- class Window implements Runnable{
- private static int num=200;
- //由于基本数据类型的资源无法用作对象锁,且它是类的静态成员,
- //因此可新建一个与共享的"基本数据类型"资源平行的对象,来代替它来做对象锁
- private static Object obj=new Object();
- private String windowName=null;
- public Window(String windowName) {
- this.windowName = windowName;
- }
- @Override
- public void run() {
- // synchronized (obj) {
- //这里如果加了锁的话就会变成只有一个窗口把所有的票全部卖完了,不加的话就会是所有的窗口一起卖,
- //而且很少出现有重复的票,但是作为软件这样做很不安全,因为在其他机器上运行很有可能会出现有重复票的现象,
- //于是应该像下面这样把锁放到while里面去
- while (true){
- //这里不能用this来代替obj
- synchronized (obj) {//同步块---基本数据类型的变量不能当作互斥锁。因为互斥锁是对象锁
- if (num > 0) {
- System.out.println(windowName + ":" + num--);
- } else {
- break;
- }
- }
- }
- // }
- }
- }
2、多线程调度
Java的多线程是抢占式的运行方式(先启动的线程抢占到资源的几率更大些)
1) setPriority()方法 :设置优先级
只要在一个线程启动之前为他调用这个方法就可以增加抢占到资源的概率,默认是5,越小抢占资源能力越强
2) sleep()方法和interrupt()方法 :Thread类的sleep()方法对当前线程操作,是静态方法,在执行sleep()方法时不释放对象锁。sleep()的参数指定以毫秒为单位的线程休眠时间。除非因为中断而提早恢复执行,否则线程不会在这段时间之前恢复执行。可以用interrupt()来提前中断sleep()方法,也可以用抛异常的方法中断。一个线程可以调用另外一个线程的interrupt()方法,这将向暂停的线程发出一个InterruptedException。变相起到唤醒暂停线程的功能。Thread类的方法interrupt(),是一种强制唤醒的技术。
[java] view
plain copy
- package thread.schedule.v1;
- public class Schedule {
- public static void main(String[] args) {
- Thread t1=new MyThread();
- Thread t2=new MyThread();
- t1.start();
- t2.start();
- try {
- Thread.sleep(2000);
- t1.interrupt();//过两秒钟的时候强制唤醒t1线程
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- class MyThread extends Thread{
- private static Object obj=new Object();
- @Override
- public void run() {
- synchronized (obj) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- System.out.println(this.getName()+"已经被唤醒");
- }
- for (int i = 1; i <= 100; i++) {
- System.out.println(Thread.currentThread().getName() + "--NO--"
- + i);
- }
- }
- }
- }
3) yield() 方法:用来使具有相同优先级的线程获得执行的机会。如果具有相同优先级的其它线程是可运行的,yield()将把线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则什么都不做。
注意:执行一次yield()方法,该线程只是放弃当前这一次机会,然后又会重新和其它线程一起抢占CPU,很可能又比其它线程先抢到。
[java] view
plain copy
- package cn.hncu.thread.schedule.v2;
- public class Schedule {
- public static void main(String[] args) {
- Thread t1=new MyThread("t1");
- Thread t2=new MyThread("t2");
- t1.start();
- // try {
- // t1.join();//这里如果用了join()就体现不出yield()方法了
- // } catch (InterruptedException e) {
- // e.printStackTrace();
- // }
- System.out.println("main..........");
- t2.start();
- }
- }
- class MyThread extends Thread{
- private static Object obj=new Object();
- private String threadName=null;
- public MyThread(String threadName) {
- this.threadName = threadName;
- }
- @Override
- public void run() {
- // synchronized (obj) {
- System.out.println(":::::::::" + threadName);
- int num = 0;
- while (this.threadName.equals("t1") && num++ < 50) {
- this.yield();//yield不会释放对象锁,因此,即使在外围环绕了synchronized也无法使该线程放弃,要一直到该线程执行完,在没有加锁的时候使用这个yield()方法的话每次t1线程到这里放弃了,但是他又会重新和t2线程抢资源
- }
- for (int i = 1; i <= 100; i++) {
- System.out.println(threadName + "--NO.--" + i);
- }
- // }
- }
- }
4) join()方法:调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。它可以实现线程合并的功能,经常用于线程的绝对调度。
[java] view
plain copy
- package thread.schedule.v2;
- public class Schedule {
- public static void main(String[] args) {
- Thread t1=new MyThread("t1");
- Thread t2=new MyThread("t2");
- t1.start();
- try {
- t1.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("main..........");
- t2.start();
- //注意,这里如果把t2放在t1之前启动的话那么t2照样会和t1抢资源,不会等t1运行完,所以没有调用join()方法的线程要后启动
- }
- }
- class MyThread extends Thread{
- private static Object obj=new Object();
- private String threadName=null;
- public MyThread(String threadName) {
- this.threadName = threadName;
- }
- @Override
- public void run() {
- System.out.println(":::::::::" + threadName);
- for (int i = 1; i <= 100; i++) {
- System.out.println(threadName + "--NO.--" + i);
- }
- }
- }
5) wait()方法:当前线程进入对象的wait pool。
6) notify()/notifyAll()方法:唤醒对象的wait pool中的一个/所有等待线程
注:wait和notify只能在它们被调用的实例的同步块内使用,而sleep()到处都可以用。
wait()和sleep()最大的区别:sleep()不释放对象锁,而wait()会释放,因此从效率方面考虑wait()方法更好。
3、死锁
死锁一:
[java] view
plain copy
- package thread.deadLock.lock1;
- public class DeadLock {
- public static void main(String[] args) {
- S s=new S();
- Thread b=new Thread(new ThreadB(s));
- Thread a=new Thread(new ThreadA(s,b));
- a.start();
- b.start();
- }
- }
- class S {
- public int a=0;
- }
- class ThreadA implements Runnable{
- private S s=null;
- private Thread b=null;
- public ThreadA(S s, Thread b) {
- this.s = s;
- this.b = b;
- }
- @Override
- public void run() {
- System.out.println("now start ThreadA------");
- synchronized (s) {//线程a先启动在这里拿到锁
- System.out.println(Thread.currentThread().getName()+"--A");
- try {
- b.join();//这里b线程调用join()方法,即原本是要等待b运行完其他线程才可以运行,可是这时候的锁还在a手中,因此出现了a线程在等待b线程,b线程在等待a线程的现象,这是一种死锁的现象
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("a="+s.a);
- }
- }
- }
- class ThreadB implements Runnable{
- private S s=null;
- public ThreadB(S s) {
- this.s = s;
- }
- @Override
- public void run() {
- System.out.println("new start ThreadB------");
- synchronized (s) {
- s.a=100;
- System.out.println(Thread.currentThread().getName()+"--B ,a="+s.a);
- }
- }
- }
死锁二:
[java] view
plain copy
- package thread.deadLock.lock2;
- public class DeadLock {
- public static void main(String[] args) {
- //如果要解决这种多资源出现的死锁,可以把多个资源打包成一个综合资源,
- //把综合资源变成一个对象锁,哪个线程一拿到锁就有全部资源了
- //在设计阶段就应该考虑到----把多线程中的每个线程所用的互斥资源图画出来--从图中看出哪些线程存在共享互斥资源,
- //然后分析是否可能存在死锁
- S1 s1=new S1();
- S2 s2=new S2();
- Thread a=new Thread(new ThreadA(s1,s2));
- Thread b=new Thread(new ThreadB(s1,s2));
- a.start();
- b.start();
- }
- }
- class S1 {
- public int a=1;
- }
- class S2 {
- public int a=2;
- }
- class ThreadA implements Runnable{
- private S1 s1=null;
- private S2 s2=null;
- public ThreadA(S1 s1, S2 s2) {
- this.s1 = s1;
- this.s2 = s2;
- }
- @Override
- public void run() {
- System.out.println("now start ThreadA------");
- synchronized (s1) {//这里a线程拿到锁
- System.out.println(Thread.currentThread().getName()+"--A");
- System.out.println("线程A输出,s1.a="+s1.a);
- System.out.println("线程A拿到锁s1,但在等待锁s2");
- synchronized (s2) {//在这里假如前面拿到锁s1的时候下面的b线程也拿到了锁s2那么这里就会出现死锁现象,下面的b线程也会出现死锁,因为a和b线程各握着彼此需要的一部分不放,因此无法继续进行下去,但也有可能在b线程没有拿到锁s2时a线程就一口气拿到锁s1和锁s2运行完了不出现死锁
- System.out.println("线程A输出,s2.a="+s2.a);
- }
- }
- }
- }
- class ThreadB implements Runnable{
- private S1 s1=null;
- private S2 s2=null;
- public ThreadB(S1 s1, S2 s2) {
- this.s1 = s1;
- this.s2 = s2;
- }
- @Override
- public void run() {
- System.out.println("now start ThreadB------");
- synchronized (s2) {
- System.out.println(Thread.currentThread().getName()+"--B");
- System.out.println("线程B输出,s2.a="+s2.a);
- System.out.println("线程B拿到锁s2,但在等待锁s1");
- synchronized (s1) {
- System.out.println("线程B输出,s1.a="+s1.a);
- }
- }
- }
- }
4、相关概念
1、创建线程和启动线程并不相同:在一个线程对新线程的Thread对象调用start()方法之前,这个线程并没有真正开始执行。Thread对象在其线程真正启动之前就已经存在了,而且其线程退出之后仍然存在。因此,仍可以控制或获取关于已创建的线程的信息,即使线程还没有启动或已经完成了。
2、结束线程:
1)线程到达其run()方法的末尾,推荐这种方法,自然结束。
2)线程抛出一个未捕获到的Exception或Error。
3)另一个线程调用一个弃用的stop()方法(不建议使用)。
3、守护程序线程(简称守护线程):我们提到过当Java程序的所有线程都完成时,该程序就退出,但这并不完全正确,因为程序中还隐藏的系统线程。随着程序的启动而启动,在运行期间一直捕捉符合它条件的处理,这样的线程就是守护线程。
5、注意问题
1、synchronized必须锁的是对象,基本数据类型的变量不能当作对象锁。
2、要保证多线程使用的是同一个互斥锁(对象锁),才能进行同步。
3、死锁的两种情况:
1)多个线程共用同一个对象锁,互相等待。
2)互相持有对方所需的资源(即每个线程都需要同时拿到多个资源才能继续执行,而多个线程都处于:各持有一部分,在等待另一部分。)
4、死锁的解决:要从设计方面去解决避免,即在设计时就考虑不能出现死锁。
罗列出所有临界资源,画分布图,从图中观察其中的死锁情况,改变其中线程的(临界)资源的获取方式。
设计原则:尽量让程序中少出现临界资源。
5、wait/notify 和 sleep方法:wait和notify只能在它们被调用的实例的同步块内使用,而sleep()到处都可以用。wait()和sleep()最大的区别:sleep()不释放对象锁,而wait()会释放,因此从效率方面考虑wait()方法更好。
6、同步设计的基本原则:同步块中(synchronized修饰)的代码越小越好!
同步块中不要写阻塞性代码(如,InputStream.read() )!
在持有锁的时候,不要对其它对象调用方法。(如果做到,可以消除最常见的死锁源头。)
7、同步概述:
同步的原理:将需要同步的代码进行封装,并在该代码上加了一个锁。
同步的好处:解决多线程的安全问题。
同步的弊端:会降低性能。
同步的前提:必须要保证有多个线程且它们在同步中使用的是同一个锁。