一、基础篇:
1、线程的定义
线程(thread)是操作系统进程中能够独立执行的实体(控制流),是处理器调度和分派的基本单位。
2、线程的属性
并发性,共享性,动态性,结构性
3、线程的状态
4、线程的调度
★主要是通过实现Runnable接口和继承Thread类来实现线程的调度和操作
a、Runnable接口(里面就一个run方法,只要通过重写run方法就可以实现自己想要的线程功能)
- public interface Runnable
- {
- public abstract void run();
- }
b、Thread线程类(继承这个类)
- public class Thread extends Object implements Runnable
- {
- public Thread()
- public Thread(String name)
- public Thread(Runnable 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) 两个线程交替运行
- 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接口的奇数/偶数序列线程
- 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个表示优先级的公有静态常量:
- public static final int MIN__PRIORITY=1
- public static final int MAX_PRIORITY=10
- public static final int NORM_PRIORITY=5
2)
Thread类中与线程优先级有关的方法有以下2个:
- public final int getPriority()
- public final void setPriority(int newPriority)
★线程对象的生命周期
Thread.State类声明的线程状态,新建态、运行态、阻塞态和等待态、终止态
★Thread类中改变和判断线程状态的方法
1) 线程启动
- public void start()
- public final boolean isAlive()
2) 线程睡眠
- public static void sleep(long millis) throws InterruptedException
3) 线程中断
- public void interrupt()
- public boolean isInterrupted()
例子(滚动字):
- 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 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:
Bank:
- package thread3.bank3;
-
- public class Bank {
-
- public static void main(String[] args) {
-
-
- 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、多线程互斥共享“栈”资源
举例:多窗口买票
- 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() {
-
-
-
-
-
- while (true){
-
- 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(),是一种强制唤醒的技术。
- 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();
- } 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,很可能又比其它线程先抢到。
- 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();
-
-
-
-
-
- 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() {
-
- System.out.println(":::::::::" + threadName);
- int num = 0;
- while (this.threadName.equals("t1") && num++ < 50) {
- this.yield();
- }
- for (int i = 1; i <= 100; i++) {
- System.out.println(threadName + "--NO.--" + i);
- }
-
- }
-
- }
4) join()方法:调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。它可以实现线程合并的功能,经常用于线程的绝对调度。
- 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();
-
- }
- }
- 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、死锁
死锁一:
- 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) {
- System.out.println(Thread.currentThread().getName()+"--A");
- try {
- b.join();
- } 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);
- }
- }
-
- }
死锁二:
- 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) {
- System.out.println(Thread.currentThread().getName()+"--A");
- System.out.println("线程A输出,s1.a="+s1.a);
- System.out.println("线程A拿到锁s1,但在等待锁s2");
- synchronized (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、同步概述:
同步的原理:将需要同步的代码进行封装,并在该代码上加了一个锁。
同步的好处:解决多线程的安全问题。
同步的弊端:会降低性能。
同步的前提:必须要保证有多个线程且它们在同步中使用的是同一个锁。