线程是程序运行的基本执行单元,一个进程中可以包含多个线程,这些线程共享这个进程中的内存空间。但是进程和进程之间是不共享内存的,都有自己的独立的运行空间。
建立线程的两种方法
1,一种方法是类去继承 Thread 类,其实是Thread自己实现了Runnable
2,用接口的方法,去实现 Runnable (用这个比较好)
创建步骤:
1,定义自己的类实现Runnable接口
2,覆盖Runnable接口中的run方法
3,通过Thread类建立线程对象
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
例子:卖票窗口,3个窗口卖100张票
两种方法的区别?
java 是单继承 如果继承 Thread 则不能在继承其它的类 ,实现接口的话还可以继承其它类,接口可以多实现的
多线程会出现安全问题
一:解决方法是加synchronized ,单线程不存在这个问题
好处:解决了安全问题
弊端:多个线程需要判断锁,较为消耗资源
1,
synchronized()
{
共享代码块
}
例:
Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)//加锁安全 这个锁的是obj
{
if(tick>0) //这句在多线程来说是有安全问题的,如果两个tick=1了,
//多个进程同时进到这里来执行,就会出现问题
System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
}
}
}
2,可以把共享代码封装在一个方法中,在调用其方法就行, 在方法上加上synchronized
例:
public synchronized void run() //加锁 这个锁的是this
{
while(true)
{
if(tick>0)
System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
}
}
或:
public void method()
{
synchronized(this) //相当于锁的是 method() 方法
{
共享代码块
}
}
3,或直接把synchronized 放在方法上,锁的是this,如果方法被static静态了,那么锁的就不是this了。(静态中也不可能有this),这个时候锁的是所在类的字节码文件对象。<类名.class>
不同锁的建立:
建立锁:
class Loak
{
static Object loak1=new Object();
static Object loak2=new Object();//两种锁
}
加入锁:
synchronized(Loak.loak1)
{
}
二:JDK1.5中提供了多线程升级解决方案
将同步synchronized替换成现实lock操作
要为特定 Lock 实例获得 Condition 实例,使用其 newCondition() 方法。
Condition 实例实质上被绑定到一个锁上。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用
采用 lock 锁来锁定代码
final Condition nl = lock.newCondition();
final Condition n2 = lock.newCondition();
用 n1.signal n2.signal 来唤醒一个指定等待线程
用 n1.signalAll n2.signalAll 来唤醒所有指定等待线程
例:
class ziyuan //资源
{
//定义锁
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock(); 代码上锁
try
{
while(!flg)
{
c1.await(); // p1 等待
}
flg = false;
c2.signal(); // v2 唤醒
}
finally
{
lock.unlock(); 最后一定要解锁
}
}
如果在一个类中有多处使用加锁,要处理同一共享数据时。要加锁同一个。不然就要出错
注意: 使用这个不要出现 死锁 ,当类中存在多个不同的锁时,而这些锁存在交叉,那么就可能会出现死锁
如:
1,锁A--11
{锁B--22}
2,锁B--33
{锁A--44}
像上面这样出现交叉的锁。如11 33 同时执行后 要运行22 44就锁住了。22被下面B锁了,44又被上面11锁住了。那么就执行不下去了
怎么让线程停下来呢?
stop已过时,只有一种方法,让 run 方法停止。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run 方法结束,也就是线程结束。
用 while 循环来判断标识。
原因:让被唤醒的线程再一次判断标记
例:
class StopThread implements Runnable
{
private boolean flag = true; //标记,用于控制线程结束
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"..........run");
}
}
public void changeFlag() //更改标记
{
flag = false;
}
}
class ThreadStopclass
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++==60)
{
st.changeFlag(); //修改标记让线程停下
break;
}
System.out.println(Thread.currentThread().getName()+"..........");
}
System.out.println("主函数运行完。。。");
}
}
特殊情况:
当线程处于了冻结状态(就是等待状态),就不会读取到标记,那么线程就不会结束。
这个时候怎么结束呢?
这时要清除冻结状态,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类中提供了该方法 interrupt() ,中断线程.
例:
public synchronized void run() //在这加了锁这种情况,所以下面等待就不会让线程停下 {
while(flag)
{
try
{
wait(); //在这里让线程等待了。所以必须先唤醒线程才能让其停止。
}
catch (Exception e)
{
System.out.println(Thread.currentThread().getName()+"................Exception");
}
ystem.out.println(Thread.currentThread().getName()+"..........run");
}
}
while(true)
{
if(num++==60)
{
st.changeFlag(); //修改标记让线程停下
t1.interrupt(); // 中断线程
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"..........");
}
System.out.println("主函数运行完。。。");
线程带来的好处?
1,充分利用CPU资源
2,简化编程模型
如果要使程序完成多项任务,这时用单线程的话,就得作出判断以及什么时候执行。而且不好操作,使用多线程来完成呢,就方便的多。
3,使GUI更有效率。
4,节约成本。
使用多进程,提高了程序的执行效率。
5,简化异步事件的处理。
线程的生命周期:开始、运行、挂起、停止 四种不同的状态。
join方法的使用
程序在返回数据的过程中,而这个数据又是在线程执行过程中给赋予的,这时如果线程没有执行结束,或还没有给我们所调用的信息赋值,那么这时,返回的数据就是错误的信息了。
这时,我们可以采用让调用者先等待一会,在去调用,但是等多长时间又是一个问题,所以在这里出现了join()方法。调用了join()方法,就是让这个线程执行完,才执行下面的代码。
例:
// 有 10 个线程
for(int i=0; i<10; i++)
{
threads[i].start();
}
//让每一个线程执行完后,再往下执行
for(int i=0; i<10; i++)
{
threads[i].join();
}
//等待线程执行完才会执行后面的代码
System.out.println();
注意:其实在这里调用join()方法,和调用其它任意一个方法,是一样的,只要在这里调用了一下,就意味着线程执行完在往下执行。
向线程传递数据的三种方法:
1,通过构造函数
2,通过方法和变量
3,通过回调函数
回调函数就是事件函数。
就是把自己给别人,让别人通过方法来给自己传值。
例:
下面我们给Data 类中value 赋值
class Data{
public int value = 0;
}
class Work{
public void process(Data data,int a)
{
data.value = a;
}
}
public static void min(String [] args)
{
Data data = new Data();
Work work = new Work();
work.process( data , 3 ); //通过回调函数给value 赋值
}
volatile 关键字
用于声明简单类型,如 int float boolean 等。
如:
public volatile int n = 9;
声明后,对它们的操作就会变成原子级别的。 每次使用它都到主存中进行读取。多线程在操作的时候操作的是同一个数据,所以保证了数据的同步。
注意:当变量值由自身的上一个值决定时,如 n=n+1 n++ 等,volatile关键字将失效。
心得:
使用线程就得注意数据同步的问题,使用join方法非常好,在线程中还有一个问题,就是在给代码加上锁的时候注意别产生死锁。