------- android培训、java培训、期待与您交流! ----------
多线程的概念
进程:正在执行的程序。
线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)
一个进程中至少有一个线程。
对于JVM,启动时,只好有两个线程:jvm的主线程。jvm的垃圾回收线程。
线程的状态。
1,被创建。
2,运行。
3,冻结。
4,消亡。(也就是结束)
其实还有一种特殊的状态:临时状态。
临时状态的特点:具备了执行资格,但不具备执行权.
冻结状态的特点:放弃了执行资格。
自定义线程(线程的创建):
Java给我们提供了对象线程这类事物的描述。该类是Thread
该类中定义了,
创建线程对象的方法(构造函数).
提供了要被线程执行的代码存储的位置(run())
还定义了开启线程运行的方法(start()).
同时还有一些其他的方法用于操作线程:
static Thread currentThead():
String getName():
static void sleep(time)throws InterruptedException:
线程中要运行的代码都是后期定义的。
创建线程的第一种方式是:
继承Thread类。原因:要覆盖run方法,定义线程要运行的代码。
步骤:
1,继承Thread类。
2,覆盖run方法。将线程要运行的代码定义其中。
3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();//创建自定义线程对象
thread.start();//调用run方法
}
}
class MyThread extends Thread{//继承Thread类
@Override
public void run() {//复写run方法
for(int i=0;i<10;i++){
System.out.println("i = "+i);
}
}
}
方式一的简写形式(匿名内部类)
new Thread(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("i = "+i);
}
}
}.start();
创建线程的第二种方式:
实现Runnable接口。
步骤:
1,定义了实现Runnable接口。
2,覆盖接口的run方法。将多线程要运行的代码存入其中。
3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。
4,调用Thread对象的start方法。开启线程。
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("i = "+i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
方式二的简写形式(匿名内部类)
new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("i = "+i);
}
}
}).start();
两种方式的特点:
实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。
综合示例:卖票小程序,
class Ticket implements Runnable{
private int tick = 100;
public void run(){
while(tick>0){//只要还有票就继续卖
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class Main{
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建了一个线程;
Thread t2 = new Thread(t);//创建了一个线程;
Thread t3 = new Thread(t);//创建了一个线程;
Thread t4 = new Thread(t);//创建了一个线程;
t1.start();
t2.start();
t3.start();
t4.start();
}
}
以上小程序多次运行后会出现0号票乃至-1号票,容易出现安全问题
线程间同步
因为多线程具备随机性。因为是由cpu不断的快速切换造成的。
就有可能会产生多线程的安全问题。
问题的产生的原因:
1,多线程代码中有操作共享数据。
2,多条语句操作该共享数据。
有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。就会发生数据错误。
解决方法:
当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作。
Java就对这种解决方式提供了专业的代码。
Synchronized:
同步的原理:就是将部分操作功能数据的代码进行加锁。
例如:将前面的买票程序加同步
class Ticket implements Runnable{
private int tick = 100;
public synchronized void run(){//同步函数的形式
while(tick>0){//只要还有票就继续卖
//synchronized(this){//同步代码块的形式
If(tick>0){
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
//}
}
}
}
}
class Main{
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建了一个线程;
Thread t2 = new Thread(t);//创建了一个线程;
Thread t3 = new Thread(t);//创建了一个线程;
Thread t4 = new Thread(t);//创建了一个线程;
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的表现形式:
1,同步代码块。
2,同步函数。
两者有什么不同:
同步代码块使用的锁是任意对象。
同步函数使用的锁是this。
注意:对于static的同步函数,使用的锁不是this。是 类名.class 是该类的字节码文件对象。
单例设计模式的懒汉式也要用到同步,不然会有安全问题。
同步的好处:解决了线程的安全问题。
同步的弊端:
较为消耗资源。
同步嵌套后,容易死锁。
同步使用的前提:
1,必须是两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
这是才可以称为这些线程被同步了。
特别注意:同步中要避免嵌套同步,容易死锁
例如:
class DeadLock{
public static void main(String[] args) {
Dead d = new Dead();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Dead implements Runnable{
boolean flag = true;
public void run(){
if (flag){
flag = false;
while(true){
synchronized(MyLock.locka)//这里用的是A对象锁
{
System.out.println(Thread.currentThread().getName()+":if locka");
synchronized(MyLock.lockb)这里用的是B对象锁
{
System.out.println(Thread.currentThread().getName()+":if lockb");
}
}
}
}
else
{
while (true){
synchronized(MyLock.lockb)//这里用的是B对象锁
{
System.out.println(Thread.currentThread().getName()+":else lockb");
synchronized(MyLock.locka)这里用的是A对象锁
{
System.out.println(Thread.currentThread().getName()+":else locka");
}
}
}
}
}
}
class MyLock//自定义锁对象
{
static MyLock locka = new MyLock();//锁A
static MyLock lockb = new MyLock();//锁B
}
由于A锁中嵌套了B锁,B锁中嵌套了A锁,很容易出现两个线程拿着各自的锁要对方的锁,造成死锁
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
Lock:替代了Synchronized
lock 上锁
Unlock 释放锁
newCondition()
释放锁的动作一定要执行.
线程间的通信:
等待/唤醒机制。
也就是常见的生产者消费者问题。
1.当多个生产者消费者出现时,
需要让获取执行权的线程判断标记。
通过while完成。
2.需要将对方的线程唤醒。
仅仅用notify,是不可以的。因为有可能出现只唤醒本方。
有可能会导致,所有线程都等待。
所以可以通过notifyAll的形式来完成 。
wait:进入等待状态
notify();唤醒线程池中等待的第一个线程
notifyAll();唤醒线程池中所有等待线程
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
class Res{//定义一个缓冲区,同时也有锁对象的作用
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable{
private Res r ;
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(r){
if(r.flag)//如果缓冲区有数据,就等待
try{r.wait();}catch(Exception e){}
if(x==0){
r.name="mike";
r.sex="man";
}
else{
r.name="张三";
r.sex = "男男男男男";
}
x = (x+1)%2;//写入值转换标记
r.flag = true;
r.notify();//唤醒消费线程
}
}
}
}
class Output implements Runnable{//消费者线程
private Res r ;
Output(Res r){//将传入的缓冲区对象赋值
this.r = r;
}
public void run(){
while(true){
synchronized(r){
if(!r.flag)/如果缓冲区没数据,就等待,否则就打印,同时改变标记
try{r.wait();}catch(Exception e){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;
r.notify();//唤醒生成线程
}
}
}
}
class Main{
public static void main(String[] args) {
Res r = new Res();//定义一个统一的锁对象,同时也是缓冲区的作用
Input in = new Input(r);//创建生产者
Output out = new Output(r);//创建消费者
Thread t1 = new Thread(in);创建生成线程
Thread t2 = new Thread(out);/创建消费线程
t1.start();
t2.start();
}
}
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待
JDK1.5版本提供了一些新的对象,优化了等待唤醒机制。
1,将synchronized 替换成了Lock接口。
将隐式锁,升级成了显示锁。
Lock
获取锁:lock();
释放锁:unlock();注意:释放的动作一定要执行,所以通常定义在finally中。
获取Condition对象:newCondition();
2,将Object中的wait,notify,notifyAll方法都替换成了Condition的await,signal,signalAll。
和以前不同是:一个同步代码块具备一个锁,该所以具备自己的独立wait和notify方法。
现在是将wait,notify等方法,封装进一个特有的对象Condition,而一个Lock锁上可以有多个Condition对象。
例如:生产者消费者
class Main{
public static void main(String[] args){
final Demo d = new Demo();
new Thread(){//消费者线程
public void run() {
while(true)
d.get();
}
}.start();
new Thread(){//生产者线程
@Override
public void run() {
while(true)
d.set();
}
}.start();
}
}
class Demo{
private boolean flag = true;//转换标记
private int num = 0;
private Lock lock = new ReentrantLock();//定义一个锁lock
Condition con_get = lock.newCondition();//定义一个condition对象con_get
Condition con_set = lock.newCondition();//定义一个condition对象con_set
public void get(){
lock.lock();
try {
while(flag)//如果是空就等待
con_get.await();
System.out.println("消费者"+num+"******");
flag = true;//改变标记
con_set.signal();//唤醒con_set
} catch(Exception e){
}finally {
lock.unlock();
}
}
public void set(){
lock.lock();
try {
while(!flag)//如果不空就等待
con_set.await();
System.out.println("生产者"+(num++)+"***");
flag = false;//改变标记
con_get.signal();//唤醒con_get
} catch(Exception e){
}finally {
lock.unlock();
}
}
}
停止线程:
stop过时。
原理:run方法结束。run方法中通常定义循环,指定控制住循环线程即可结束。
1,定义结束标记。
2,当线程处于了冻结状态,没有执行标记,程序一样无法结束。
这时可以循环,正常退出冻结状态,或者强制结束冻结状态。
强制结束冻结状态:interrupt();目的是线程强制从冻结状态恢复到运行状态。
但是会发生InterruptedException异常。
线程中一些常见方法:
yield():临时暂停,可以让线程是释放执行权。
setDaemon(boolean):将线程标记为后台线程,后台线程和前台线程一样,开启,一样抢执行权运行,
只有在结束时,有区别,当前台线程都运行结束后,后台线程会自动结束。
join():什么意思?等待该线程结束。当A线程执行到了B的.join方法时,A就会处于冻结状态。
A什么时候运行呢?当B运行结束后,A就会具备运行资格,继续运行。
加入线程,可以完成对某个线程的临时加入执行。
wait和sleep的区别:
wait:释放cpu执行权,释放同步中锁。
sleep:释放cpu执行权,不释放同步中锁。