线程:通俗的讲就是进程的儿子,进程中包含线程,且最少包含一个线程
比如说JVM启动的时候有进程java.exe---该进程中至少有一个线程负责java程序的运行
而且这个线程运行的代码存在于main方法----称为主线程
和异常一样,java已经提供了对线程的描述,我们只需要继承Thread或实现Runable接口就可以成为线程
创建线程的步骤:
继承Thread类或实现Runnable接口
实现好处是避免了单继承的局限性
当一个类是另一个类的子类,但是该类中还必须使用多线程,此时采用实现方式
复写run方法
Thread类定义了一个功能
用于存储线程要运行的代码,该存储功能就是run方法
Thread类中的run方法-----也就是线程的存储空间
Start启动线程
虚拟机定义主线程运行的代码存放在main方法中-----因为程序要从main开始
自定义线程的代码存放位置是覆盖父类的run方法
一个简单的线程例子:
/*
创建两个线程,和主线程交替运行
线程都有自己默认的名称
Thread-编号 该编号从0开始
可以设置线程名称:setName或者构造函数
*/
class Test extends Thread
{
Test(String name)
{
super(name); //父类中已经定义,只需要调用即可
}
public void run()
{
for(int x=0;x<60;x++)
{
//System.out.println(this.getName()+"run...."+x);
System.out.println(Thread.currentThread().getName()+"run...."+x);//与上面相同
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Test t1 = new Test("one----");
Test t2 = new Test("two++++");
t1.start();
t2.start();
for(int x=0;x<60;x++)
{
System.out.println("main run===="+x);
}
}
}
多线程运行中存在的安全问题:
当多个线程操作共享数据时,容易发生多线程安全问题
当A线程还没操作完成后,B线程进入操作
因为是在同一个run方法中,容易发生安全问题
场景:单身模式的懒汉式
class Signal
{
private static Signal s =null;
private Signal(){}
public static Signal getInstance()
{
if(s==null)
{
s=new Signal ();
}
return s;
}
}
/*
假设两个线程,t1刚进入if语句就挂掉了,
此时t2进入if语句时,判断s是null,因为t1在还没执行new对象的时候就挂了
所以造成的结果是两个对象指向的不是同一个
*/
出现安全问题后,可通过同步来进行解决
同步包括同步代码块和同步函数
解决办法是:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
同步的前提:
1、必须要有两个或两个以上的线程
2、必须是多个线程使用同一个锁
必须保证同步中只能有一个线程-------->保证线程的安全
同步的好处是解决了多线程中的安全问题
但是同时也消耗了资源,每个线程都需要判断锁
同步代码块需要创建锁,持有锁的线程才可以进入同步代码块或同步函数
锁可以是任意类的对象,这里引用Object类的对象
Object obj = new Object();
public void add(int n)
{
synchronized(obj) //obj是一个锁,监听器,
{
sum = sum+n;
System.out.println("sum= "+sum);
}
}
锁机制相当于:
多个线程(A,B,C)进入以后,假设A线程先抢到执行权
进入锁,此时A持有锁,并将锁的某个状态位改为0,若在A出去之前,A被冻结
失去执行资格,B,C也无法进入,因为侦测到状态位为0,即没有获得锁
同步函数:在函数名前面用synchronized修饰
同步函数的锁是this
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
当同步函数被static修饰后,其锁就不再是this
因为静态函数是属于类的,this不可以出现在静态函数中
静态函数进入内存时,内存中没有本类对象,但是一定有该类字节码文件的对象
class Ticket implements Runnable //extends Thread
{
private static int tick = 100;
Object obj = new Object(); //创建锁
boolean flag = true ;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)//静态函数的锁是类名.class,因为静态函数中不牵扯this,若函数不是静态,则须改为this
{ if(tick>0)
{
try{Thread.sleep(5);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code"+tick--);
}
}
}
}else
{
while(true)
show();
}
}
public static synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(5);}catch(Exception e){}//多线程运行出现了错误,不能抛出异常,只能try,因为run是继承的
System.out.println(Thread.currentThread().getName()+"........show"+tick--);
}
}
}
class StaticMethodDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
//利用线程切换,否则主线程有可能在执行时,把三条语句一次性执行完,那flag永远都是false
//所以需要先让t1执行,然后让主线程睡会,然后在设置false,达到切换的效果
try{Thread.sleep(10);}catch(Exception e){}
t.flag=false;
t2.start();
}
}
程序在某些时刻会发生死锁
当在同步中嵌套同步,但是锁不同,就会发生死锁。
/*
出现的情况可能是
if中的同步代码块获得a锁后,要b锁
else中的同步代码块获得b锁后,要a锁,
互不相让,就会发生死锁
也可以让两个同步代码块互相包含
*/
class MyLock
{
static Object locka=new Object(); //创建a锁
static Object lockb=new Object(); //创建b锁
}
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if ..locka....");
synchronized(MyLock.lockb)
{
System.out.println("if ...lockb...");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else ....lockb..");
synchronized(MyLock.locka)
{
System.out.println("else ....locka..");
}
}
}
}
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
除此之外,死锁还可能是
Obj锁中包含this锁,This锁中包含Obj锁
0线程拿着Obj锁,要this锁,1线程拿着this锁,要Obj锁