JAVA 多线程得创建,以及资源共享的问题。

java 多线程技术

java 多线程不愧是java语言的核心部分和难点部分,想把他学好,理解同其实真的是挺不容易的,在经过几天的看书之后,现在打算来总结一下吧。
创建多线程的方式:

  • 1 使用继承的方式:
    dome 如下:
public class MyThread extends Thread {
    public static void main ( String args[] ) {                
        Thread a = new MyThread();  //(1) 创建线程实例
        a.start();                  //(2) 启动线程
        System.out.println("This is main thread."); //(3)
    }
    public void run() {
        System.out.println("This is another thread."); //(4)
    }                                                          
}

启动线程使用.start 方法.run方法不产生线程哦。

  • 2 利用接口产生线程:
public class MyThread implements Runnable {
    public static void main ( String args[] ) {                
        MyThread my = new MyThread(); //(1) 
        Thread a = new Thread(my);    //(2) 创建线程实例,实现Runnable接口的类对象
                                            作为实参
        a.start();                    //(3)
        System.out.println("This is main thread.");  //(4)
    }
    public void run() {
        System.out.println("This is another thread.");//(5)
    }                                                          
}

线程对象去调用Runnable 接口实现的对象。

  • 3 使用Callable 接口,该接口object 可以为任意包装类,代表你所要返回的数据类型。
    Dome 如下:

import java.util.ArrayList;
import java.util.concurrent.*;

public class CallableDome {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService exce = Executors.newCachedThreadPool();
        ArrayList<Future<String>> result = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++) {
            result.add(exce.submit(new TaskWithResult(i)));
        }
        for (Future<String> string : result) {
            System.out.println(string.get());
        }
    }
}


import java.util.concurrent.Callable;

public class TaskWithResult implements Callable<String > {
  int id;

TaskWithResult(int id)
{this.id=id;}

    @Override
    public String call() throws Exception {
        return "hello "+id;
    }
}

总结:1 所代表创建线程的方式,最为单调,最普通的逻辑,无返回至,不需要共享资源的使用1,第二种方式可用于,需要多个线程处理一个对象且无返回值,而第三个的话有返回值,有资源处理,且一般搭配线程池来使用。

多个线程对同一对象进行处理时的资源共享的问题:

首先看一段代码:


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestThread{
    public static void main(String[] args){
        Resource m = new Resource(1000);
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        t1.start();
        t2.start();
    }
}
class Resource implements Runnable {
    private Lock mylock=new ReentrantLock();
    public int i;
    public  Resource(int _i){
        i = _i;
    }
    public void run(){

            while (true) {
                if (i > 0) {
                    i--;
                    System.out.println(Thread.
                            currentThread().getName() + " " + i);
                } else break;
         
               
            }

        }
    }


这段代码会导致一个问题:
会出现输出的数字相同的情况。这里出现问题的原因就是因为资源共享的问题。
例如:i--  这个步骤不具备原子性,可分为3部分,
1 虚拟机从内存中取值,放入寄存器中。
2 对寄存器中i进行–操作。
3 将值返回到内存中。
如果某一线程进入2过程时,有其他线程抢占了资源,并完成3这就会导致了,原先的线程对i的操作冲突,导致出现相同的结果。

== 解决方法:==

  • 使用Lock 对象,生成一个锁对象,对数据进行管理。
    方法如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestThread{
    public static void main(String[] args){
        Resource m = new Resource(1000);
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        t1.start();
        t2.start();
    }
}
class Resource implements Runnable {
    private Lock mylock=new ReentrantLock();
    public int i;
    public  Resource(int _i){
        i = _i;
    }
    public void run(){

            while (true) {
                mylock.lock();
                try
                {if (i > 0) {
                    i--;
                    System.out.println(Thread.
                            currentThread().getName() + " " + i);
                } else break;}finally {
                    mylock.unlock();
                }
            }

        }
    }


将你需要保持原子性的代码给包括在内,记得必须要使用的格式:

mylock.lock();
try{  代码块}
finally{mylock();}

因为你所写的代码块中,可能会出现异常,导致线程被阻塞,不能进入下一步,导致死锁的问题,使用finally之后,哪怕出现异常也能将锁打开,从而使其他线程继续进行,而不是产生死锁。

与Lock对象有关的一个对象,条件对象:

    private Lock mylock=new ReentrantLock();
    private Condition myCondition=mylock.newCondition();

该对象的方法:void await();
将该线程放入条件等待集之中。
void signalAll()
解除等待集中所有线程的阻塞状态。
void signal ( )
从该条件的等待集中随机地选择一个线程, 解除其阻塞状态。

  • 使用synchronized关键字。
    这可将一段代码,在运行的时候置为唯一运行的,当某一线程运行时,其他线程就被阻塞了。也可用在方法上,将方法都置为是唯一运行的
    用法如下:
class Resource implements Runnable {
  volatile public int i;        
  volatile public Integer it;
  public  Resource(int _i){
    i = _i;
    it = new Integer(i);
  }
  public  void run(){
   while(true){
    synchronized(it){
     if (i>0){
        try{
           Thread.sleep(200);
        }
        catch(Exception e){}
        i--;
        System.out.println(Thread.
        currentThread().getName()+"  "+i);
    }
    else{
        System.out.println(Thread.
        currentThread().getName());
        break; }}
}}}

public class TestSecurity{
public static void main(String[] args){
  Resource m = new Resource(9);
  Thread t1 = new Thread(m);
  Thread t2 = new Thread(m);
  t1.start();
  t2.start();
}
}

同步块又称为临界区,保证同一时间只有一个线程执行同步块内的代码。

  • volatile 关键字的使用。
    volatile 关键字的使用,主要是用于一些不涉及复杂操作的变量,加入某个变量,被使用,volatile 关键字,标识之后,他所进行的一些简单语句,如:赋值,++,–等,就不会出现原子性的问题,如i–;
    1 虚拟机从内存中取值,放入寄存器中。
    2 对寄存器中i进行–操作。
    3 将值返回到内存中。
    这三个步骤将被一个线程,一次性执行,中间不会被某些其他的线程阻塞导致结果出错。
    想要对一些线程的变量进行运算时,为保持其操作的原子性,Java中还提供了一些方法,帮助实现。
    例如:
public static AtomicLong nextNumber = new AtomicLongO ;
// In some thread...
long id = nextNumber.increinentAndGetO:

使用该对象可对其-1,且不受线程的干扰。

如果要进行较为复杂的运算的话,使用AtomicLong 显然是不够的,这个事后就需要,一种更为通用的方法,例如使用compareAndSet 方法。
这个更新不是原子的。实际上,应当在一个循环中计算新值和使用 compareAndSet:

do {
oldValue = largest.getO;
newValue = Math ,max (oldValue , observed);
} while (!largest.compareAndSet(oldValue, newValue));

这里简单来说有一点坑,这个函数不太好想他的作用,
他的作用大概就是这样,因为会先获取到largest里面所得到的值,然后又获得需要更新的值,传给了compareAndSet 对象,如果说这个时候,又其他线程成功的将compareAndSet方法执行完,这个时候
compareAndSet 内部的值已经不和传进去的oldvalues的值相等,这个时候返回false,这个时候继续执行循最终, 它会成功地用新值替换原来的值。这听上去有些麻烦, 不过 compareAndSet 方
法会映射到一个处理器操作, 比使用锁速度更快。
 但也有一个缺点,加入非常多的线程都同时去执行compareAndSet的代码块,然后就会导致大量竟争,导致性能大幅度下降,这个时候就需要其他的对象LongAdder

LongAccumulator adder = new LongAccumulator(Long::sum, 0);
// In some thread...
adder.accumulate(value);

死锁问题

不当的程序设计逻辑都可能会导致死锁的发生,你只能更改逻辑,使其尽量不会发生死锁现象,Java本身是没有对发生死锁时进行规避的机制,不能滥用锁,需考虑好锁的使用。

线程局部变量问题:

假设你的某个类需要一个产生随机数的成员对象,但是该类实例化一个对象之后,你会使用多个线程去调用这个对象,当用到产生随机数的成员函数的时候,就会很低效,这个时候你就需要一个线程局部变量,专属于你所创建的多线程
代码如下:

public static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
//要访问具体的格式化方法,可以调用:
String dateStamp = dateFormat.get().format(new DateO);

在一个给定线程中首次调用 get 时, 会调用 initialValue 方法。在此之后, get 方法会返回
属于当前线程的那个实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值