多线程基础知识:
线程:就是进程中一个负责程序执行的控制单元(执行路径),一个进程中可以包含多个线程,称之为多线程。
一个进程当中至少要有一个线程。
开启多个线程是为了同时运行多部分代码,每个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务。
好处:解决了多部分同时运行的问题
弊端:线程太多导致效率降低。
其实程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。
JVM启动时就启动了多个线程,至少有两个线程:执行main函数的线程(该线程任务代码都定义在main函数中),负责垃圾回收机制的线程。
如何创建一个线程?
1.继承Thread类,
创建线程的目的就是为了开启一条执行路径,去运行指定的代码,并且是实现与其它代码同时运行。
而运行的指定代码就是这个执行路径的任务。
自定义线程的任务存放位置:
JVM创建的主线程的任务都定义在了主函数中,而Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务描述。
这个任务就是通过Thread类中的run方法来体现的。简而言之,run方法就是封装自定义线程运行任务的函数。
run方法中定义就是线程要运行的任务代码。开启线程就是为了运行指定代码。所以只有继承Thread类并重写run方法。并将制定运行的代码定义在润方法中。
步骤:
1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread类子类的对象,创建线程。
4.调用start()方法,开启线程并调用线程的run方法执行
可以通过Thread的getName()获取线程的名称:Thread——现成的编号(从0开始)。主线程的名字main。
2.实现runnable接口
步骤:
1.定义类实现runnable接口
2.实现接口中的run()方法,将线程的任务代码封装到run方法中
3.通过Thread类创建线程的对象,并将runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
因为线程的任务都封装在runnable接口子类对象的run方法中,只有作为实参传递参能运行任务代码。所以在创建线程对象时就要明确要运行的任务代码。
4.调用线程的start方法开启线程。
runnable的出现仅仅是将线程的任务进行了对象的封装。
CPU的执行资格:可以随时被CPU处理的状态
CPU的执行权:正在被CPU执行的状态。
释放执行权的同时释放执行资格,线程就有运行状态走到冻结状态。
3.继承Thread与实现Runnable的主要区别(Runnable的好处):
实现Runnable:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。即:按照面向对象的思想将任务封装成对象。
2.避免java单继承的局限性。所以,使用接口实现的方法更常用。
4.多次启动一个线程是非法的。
同一资源多个线程使用时(如火车卖票),防止重复数据利用(重复买票)的方法:
1.将车票资源静态化。(不利于资源变更,或者卖多种票)。
1.存在多个线程操作共享数据
2.每个线程中操作共享数据的代码有多条。(产生时间差)
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全性问题的产生。
解决思路:将多条操作共享数据的线程代码封装起来,当有现成在执行这些代码的时候,其他线程是不可以参与运算的。
必须要等待当前线程把这些代码全部执行完毕后,其他线程才可以参与运算。
3.synchronized:同步代码块。
1.放在对向前,限制一段代码格式:
synchronized (对象)//对象:
{
}
2.修饰方法的格式:
修饰词 (public,static)synchronized 返回值类型 方法名()
{
//方法体
}
注意: 同步函数的锁是this。
静态同步函数的锁该函数所属的字节码文件对象。即:getClass()获取也可用类名.class表示。
同步函数和同步代码块的区别:
同步函数的锁固定为this。而同步代码块的锁是任意的。建议使用同步代码块的锁。
4. 同步的好处:解决了线程的安全问题。
同步的弊端:相对降低效率。因为同步外的线程都会判断同步锁。
5.同步的前提:
同步中只有多线程的情况才有同步的必要。
同步中只有多个线程使用的是同一个同步锁。
6.线程间通讯:
多个线程在处理同一资源,但是任务不同。
7.等待/唤醒机制:
1.wait():让线程处于冻结状态,释放线程的执行权和执行资格。被wait的线程被存储在线程池中。
2.notify():用于唤醒线程池中的一个(需注意:是随机的一个)线程。使线程具有执行资格。
3.notifyAll():用于唤醒线程池中的所有线程。
这些方法都必须定义在同步中。因为这些方法都是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。锁名.notify()。
为什么操作线程的方法wait,notify,notifyAll放在了Object类中?
因为这些方法都是监视器方法。监视器其实就是一个锁,锁可以是任意的对象,任意的对象调用的方法必须定义在Object类中。
8.多生产者多消费者问题:
while判断标记,解决了线程获取执行权后受否需要运行。if判断标记只运行一次,会导致不该运行的线程运行了出现数据错误的情况。
notifyAll解决本方线程一定会唤醒对方线程。notify只唤醒一个线程,如果唤醒本方线程则没有意义。问题没有得到解决。
而使用while+noyify会出现死锁问题。
只有while+notifyAll才能完全解决问题。
9.JDK 1.5之后,将同步和锁封装成了对象,并将操作锁的隐式方式定义带了该对象当中,将隐式动作转换成显示动作。
lock.lock()
代码;
lock.unlock()
10.
Lock接口:它的出现替代了同步代码块和同步函数,将同步的隐式锁操作转换成了显示锁操作。同时更为灵活,可以在一个锁上添加多个监视器。
lock():获取锁。
unlock():释放锁。 通常定义在finally代码块中。
Condition接口:它的出现替代了Object类中的wait,notify和notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。
await():等待
signal():唤醒一个线程
signalAll():唤醒所有线程
11.wait和sleep的区别:
1.wait可以指定或者不指定时间,sleep必须指定时间。
2.在同步中时,对CPU的执行权和锁的处理不同。wait释放CPU的执行权,释放锁。而sleep释放执行权,不释放锁。
12.停止线程的方式:
1.stop方法:已经过时。
2.run方法结束。
怎么控制线程任务的结束?
任务中都有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记来完成。使用条件标记来结束线程。
但是如果线程处于了冻结状态,就无法读取标记。
3.可以使用interrupt方法强制将线程从冻结状态强制恢复成可运行状态中来,让线程具备CPU的执行资格,这时的强制动作会引起InterruptException异常,必须要对其处理。
13.守护线程setDeamon:
守护线程:即后台线程,用户线程,其他都和一般线程(前台线程)一样,就是结束的时候不需要手动结束。
线程安全问题出现的事例:
线程:就是进程中一个负责程序执行的控制单元(执行路径),一个进程中可以包含多个线程,称之为多线程。
一个进程当中至少要有一个线程。
开启多个线程是为了同时运行多部分代码,每个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务。
好处:解决了多部分同时运行的问题
弊端:线程太多导致效率降低。
其实程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。
JVM启动时就启动了多个线程,至少有两个线程:执行main函数的线程(该线程任务代码都定义在main函数中),负责垃圾回收机制的线程。
如何创建一个线程?
1.继承Thread类,
创建线程的目的就是为了开启一条执行路径,去运行指定的代码,并且是实现与其它代码同时运行。
而运行的指定代码就是这个执行路径的任务。
自定义线程的任务存放位置:
JVM创建的主线程的任务都定义在了主函数中,而Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务描述。
这个任务就是通过Thread类中的run方法来体现的。简而言之,run方法就是封装自定义线程运行任务的函数。
run方法中定义就是线程要运行的任务代码。开启线程就是为了运行指定代码。所以只有继承Thread类并重写run方法。并将制定运行的代码定义在润方法中。
步骤:
1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread类子类的对象,创建线程。
4.调用start()方法,开启线程并调用线程的run方法执行
可以通过Thread的getName()获取线程的名称:Thread——现成的编号(从0开始)。主线程的名字main。
2.实现runnable接口
步骤:
1.定义类实现runnable接口
2.实现接口中的run()方法,将线程的任务代码封装到run方法中
3.通过Thread类创建线程的对象,并将runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
因为线程的任务都封装在runnable接口子类对象的run方法中,只有作为实参传递参能运行任务代码。所以在创建线程对象时就要明确要运行的任务代码。
4.调用线程的start方法开启线程。
runnable的出现仅仅是将线程的任务进行了对象的封装。
CPU的执行资格:可以随时被CPU处理的状态
CPU的执行权:正在被CPU执行的状态。
释放执行权的同时释放执行资格,线程就有运行状态走到冻结状态。
3.继承Thread与实现Runnable的主要区别(Runnable的好处):
实现Runnable:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。即:按照面向对象的思想将任务封装成对象。
2.避免java单继承的局限性。所以,使用接口实现的方法更常用。
4.多次启动一个线程是非法的。
同一资源多个线程使用时(如火车卖票),防止重复数据利用(重复买票)的方法:
1.将车票资源静态化。(不利于资源变更,或者卖多种票)。
2.使用实现Runnable方法,创建一个线程任务对象。多个线程使用一个资源。(方便使用)
线程同步问题:
5.线程安全性问题产生的原因:1.存在多个线程操作共享数据
2.每个线程中操作共享数据的代码有多条。(产生时间差)
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全性问题的产生。
解决思路:将多条操作共享数据的线程代码封装起来,当有现成在执行这些代码的时候,其他线程是不可以参与运算的。
必须要等待当前线程把这些代码全部执行完毕后,其他线程才可以参与运算。
3.synchronized:同步代码块。
1.放在对向前,限制一段代码格式:
synchronized (对象)//对象:
{
}
2.修饰方法的格式:
修饰词 (public,static)synchronized 返回值类型 方法名()
{
//方法体
}
注意: 同步函数的锁是this。
静态同步函数的锁该函数所属的字节码文件对象。即:getClass()获取也可用类名.class表示。
同步函数和同步代码块的区别:
同步函数的锁固定为this。而同步代码块的锁是任意的。建议使用同步代码块的锁。
4. 同步的好处:解决了线程的安全问题。
同步的弊端:相对降低效率。因为同步外的线程都会判断同步锁。
5.同步的前提:
同步中只有多线程的情况才有同步的必要。
同步中只有多个线程使用的是同一个同步锁。
6.线程间通讯:
多个线程在处理同一资源,但是任务不同。
7.等待/唤醒机制:
1.wait():让线程处于冻结状态,释放线程的执行权和执行资格。被wait的线程被存储在线程池中。
2.notify():用于唤醒线程池中的一个(需注意:是随机的一个)线程。使线程具有执行资格。
3.notifyAll():用于唤醒线程池中的所有线程。
这些方法都必须定义在同步中。因为这些方法都是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。锁名.notify()。
为什么操作线程的方法wait,notify,notifyAll放在了Object类中?
因为这些方法都是监视器方法。监视器其实就是一个锁,锁可以是任意的对象,任意的对象调用的方法必须定义在Object类中。
8.多生产者多消费者问题:
while判断标记,解决了线程获取执行权后受否需要运行。if判断标记只运行一次,会导致不该运行的线程运行了出现数据错误的情况。
notifyAll解决本方线程一定会唤醒对方线程。notify只唤醒一个线程,如果唤醒本方线程则没有意义。问题没有得到解决。
而使用while+noyify会出现死锁问题。
只有while+notifyAll才能完全解决问题。
9.JDK 1.5之后,将同步和锁封装成了对象,并将操作锁的隐式方式定义带了该对象当中,将隐式动作转换成显示动作。
lock.lock()
代码;
lock.unlock()
10.
Lock接口:它的出现替代了同步代码块和同步函数,将同步的隐式锁操作转换成了显示锁操作。同时更为灵活,可以在一个锁上添加多个监视器。
lock():获取锁。
unlock():释放锁。 通常定义在finally代码块中。
Condition接口:它的出现替代了Object类中的wait,notify和notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。
await():等待
signal():唤醒一个线程
signalAll():唤醒所有线程
11.wait和sleep的区别:
1.wait可以指定或者不指定时间,sleep必须指定时间。
2.在同步中时,对CPU的执行权和锁的处理不同。wait释放CPU的执行权,释放锁。而sleep释放执行权,不释放锁。
12.停止线程的方式:
1.stop方法:已经过时。
2.run方法结束。
怎么控制线程任务的结束?
任务中都有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记来完成。使用条件标记来结束线程。
但是如果线程处于了冻结状态,就无法读取标记。
3.可以使用interrupt方法强制将线程从冻结状态强制恢复成可运行状态中来,让线程具备CPU的执行资格,这时的强制动作会引起InterruptException异常,必须要对其处理。
13.守护线程setDeamon:
守护线程:即后台线程,用户线程,其他都和一般线程(前台线程)一样,就是结束的时候不需要手动结束。
设置守护线程的方法:线程对象.setDeamon(boolean on);必须在线程启动前调用。
下面一个简单的弹球程序演示线程的创建等简单问题:
import java.awt.*;
import java.awt.event.*;
/* 定义弹子类 */
class Marble extends Thread
{
Table table=null;
int x,y,xdir,ydir;
public Marble(Table _table,int _x,int _y,int _xdir,int _ydir)
{
table=_table; //使用该参数,是为了能获取到窗口的大小
x=_x; //x坐标
y=_y; //y坐标
xdir= _xdir; //x方向速度
ydir= _ydir; //y方向速度
}
public void run()
{
while(true)
{
if((x>(table.getSize().width)-25)||(x<0))
xdir*=(-1); //超过台子x方向边界后,反方向运行
if((y>(table.getSize().width)-25)||(y<0))
ydir*=(-1); //超过台子y方向边界后,反方向运行
x+=xdir; //坐标递增 以实现移动
y+=ydir;
try{ sleep(30); //延时时间(1/刷新率)
} catch(InterruptedException e)
{System.err.println("Thread interrupted");}
table.repaint(); //重绘图形
}
}
public void draw(Graphics g)
{
g.setColor(Color.black); //弹子为黑色
g.fillOval(x,y,30,30); //画圆
g.setColor(Color.white); //弹子上的亮点为白色
g.fillOval(x+5,y+5,8,6);
}
}
/* 定义弹子球台类 */
class Table extends Frame implements ActionListener
{
Button start=new Button("开始");
Marble marbles[]=new Marble[5]; //建立弹子线程类对象数组
int v=2; //速度最小值
public Table()
{
super("弹子台球");
setSize(300,300);
setBackground(Color.cyan); //背景
setVisible(true);
setLayout(new FlowLayout());
add(start);
start.addActionListener(this);
validate();
addWindowListener(new WindowAdapter()
{
public void windowClosing (WindowEvent e)
{System.exit(0);}
} );
}
public void actionPerformed(ActionEvent ae)
{
for(int i=0;i<marbles.length;i++)
{ //随机产生弹子的速度和坐标
int xdir=i*(1-i*(int)Math.round(Math.random()))+v;
int ydir=i*(1-i*(int)Math.round(Math.random()))+v;
int x=(int)(getSize().width*Math.random());
int y=(int)(getSize().height*Math.random());
//实例化弹子线程对象
marbles[i]=new Marble(this,x,y,xdir,ydir);
marbles[i].start();
}
}
public void paint(Graphics g)
{
for(int i=0;i<marbles.length;i++)
if(marbles[i]!=null)
marbles[i].draw(g);
}
}
/* 定义主类 */
public class Example7_5
{
public static void main(String args[])
{
Table table=new Table();
}
}
线程安全问题出现的事例:
class Process
{
public static void main(String[] args)
{
P p1 = new P();
P p2 = new P();
Thread t1 = new Thread(p1,"线程1");
Thread t2 = new Thread(p1,"线程2");
t1.start();
t2.start();
}
}
class P implements Runnable
{
int num = 100;
public void run()
{
while(num>0){
System.out.println(num+":"+Thread.currentThread().getName());//;1
num--;//2
}
}
}
/*
出错情况:代码中的1,2,运行之间可能出现线程转换,致使num还没来得及减少,就又被另一线程使用,出现重复使用的情况。
100:线程1
100:线程2
99:线程1
97:线程1
*/