您的位置:首页 > 职场人生

黑马程序员---java基础之多线程

2013-03-14 18:19 567 查看
-------
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执行权,不释放同步中锁。

------- android培训java培训、期待与您交流!
----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: