多线程的新增创建方式
实现Callable接口实现多线程的创建
基本的步骤:
1、新建一个类Test,从而实现Callable这个接口
2、在Test这个类中,重写Callable这个接口的call方法,从而设置线程的执行内容。注意这个call方法有返回值,其返回值类型为Object,并且声明了异常。
3、在主函数中,新建Test类(即Callable接口的子类)的对象test
4、新建FutureTask这个类的对象f,并且它的构造方法的参数就是上面刚刚新建的Test类的对象test
5、新建Thread类的对象thread,并且它的构造方法的参数是FutureTask类对象f。
6、通过调用Thread类的对象thread的start方法,从而启动线程,然后执行Callable子类中的call方法。
代码实例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//步骤1、新建一个子类,使得这个类实现Callable接口
class Test implements Callable {
@Override
//步骤2、重写Callable接口的call方法,从而设置线程的内容,注意这个call方法有返回值,并且声明了异常
public Object call() throws Exception {
int sum = 0;
for(int i = 0; i<=10; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+": "+i);
sum += i;
}
}
return sum;
}
}
public class CallableImpl {
public static void main(String[] args){
//步骤3、在主函数中,新建Callable子类对象
Test test = new Test();
//步骤4、新建FutureTask类对象,并且它的构造方法的参数是上面刚刚新建的Callable子类对象
FutureTask f = new FutureTask(test);
//步骤5、新建Thread类对象,并且它的构造方法的参数是上面刚刚新建的FutureTask对象
Thread thread = new Thread(f);
thread.start();//步骤6、通过Thread类对象,调用start方法,从而启动线程
//步骤7、如果要获取callable子类中的call方法的返回值,那么就要调用上面FutureTask对象的get方法获取,不过
//需要注意要用try/catch包住,因为call方法声明了异常
try {
Object result = f.get();//获取结果
System.out.println("结果为:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
结果为:
注意这里实现Callable接口创建线程和实现Runnable接口创建线程相比,前者功能更强大:
①前者中的call方法含有返回值,但是如果通过实现Runnable接口创建线程,那么run方法则没有返回值。
②前者在方法声明后抛出了异常,表明了可能会出现异常,但是后者并没有声明可能会抛出异常,只能在run方法内部用try/catch异常。
③Callable接口支持泛型,但是后者不支持泛型。
Runnable接口:
public interface Runnable {//不支持泛型
public abstract void run();//没有返回值,没有声明异常
}
Callable接口:
public interface Callable<V> {//Callable接口支持泛型
V call() throws Exception;//有返回值,并且声明了异常
}
通过线程池实现多线程
线程1代码(通过实现Runnable接口创建线程):
class RunnableSubClass implements Runnable{
@Override
public void run() {
for(int i = 0; i<10; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}
线程2(通过实现Callable接口创建线程):
class CallableSubClass implements Callable {
@Override
public Object call() throws Exception {
for(int i = 0; i<10; i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
return null;
}
}
线程3(通过继承创建线程):
class ThreadSubClass extends Thread{
@Override
public void run(){
for(int i = 0; i<10; i++){
if(i % 3 == 0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}
测试类代码:
public class PoolThread {
public static void main(String[] args){
//步骤一:新建线程池,newFixedThreadPool()里的参数表示线程池中的线程数
ExecutorService service = Executors.newFixedThreadPool(3);
//步骤二:创建线程,将线程存放到线程池中,并进行执行
//通过实现Runnable接口创建线程,然后将线程放到线程池中
RunnableSubClass subClass1 = new RunnableSubClass();//创建Runnable接口的子类
Thread thread = new Thread(subClass1);//新建线程
thread.setName("线程1");
//通过实现Callable接口创建线程,然后将线程放到线程池中
CallableSubClass subClass2 = new CallableSubClass();
FutureTask f = new FutureTask(subClass2);
Thread thread1 = new Thread(f);
thread1.setName("线程2");//将线程2添加到线程之后,如果通过这样的方式修改线程的名字,实际上没有修改
//通过继承创建线程
ThreadSubClass thread2 = new ThreadSubClass();
//将线程存放到线程池中,并执行
service.execute(thread);//调用execute方法,适用于Runnable接口
service.submit(thread1);//调用submit方法,适用于Callable接口
service.submit(thread2);
//步骤三:线程执行完毕之后,将线程池关闭,如果不关闭,虽然没出现异常,也没有错误提示,但是会一直运行
service.shutdown();
}
}
结果为:
这里有几点不太明白:execute()适用Runnable接口,submit()适用于Callable接口,那么为什么execute里面的参数可以是通过实现Callable接口的线程?为什么可以是通过继承实现的线程?同样的,为什么submit()里的参数可以是通过实现Runnable接口的线程?为什么可以是通过继承实现的线程?如果有大佬知道的,请大家指教。
线程的同步
线程的同步,用到了一个关键字synchronized。
同步代码块
对于通过实现Runnable接口创建的线程来说,synchronized()括号里的参数,即同步监视器(俗称锁)可以用this来表示,因为这个类就是被所有的线程所共享的,因此可以用this来表示。但是如果是通过继承来创建的线程来说,则需要慎用this,因为无法保证这把锁能够被多个线程所共用,所以要慎用,但是它可以用子类的类名.class,即synchronized(类名.class)。
格式:
synchronized(同步监视器){
//操作共享数据的代码,不可以多,也不可以少
}
所谓的共享数据:就是多个线程共同操作一个变量,就好像下面的实例中的票
同步监视器:俗称锁,任何一个类的对象都可以作为锁,要求是多个线程必须共同使用同一个锁。
同步方法
对于通过实现Runnable接口创建的线程来说,同步方法只要将需要被同步的方法被synchronized修饰即可,但是对于通过继承实现的线程来说,不单单是要求这个方法被synchronized修饰就可以了,还要求他是静态的,只有这样才能保证被所有的对象所共享。注意在实现Runnable接口创建的线程中,同步监视器默认为this,但是通过继承创建的线程来说,同步监视器则默认为类名.class。
实例:多窗口卖票
方法一:同步方法实现,从而解决线程的安全问题:
1、通过继承来创建的线程:
class ThreadTest5 extends Thread{
private static int ticket = 10;//共享数据,所以定义为静态的,所以涉及到ticket的代码一定要在同步方法中,否则会出现只有一个线程或者出现错票的问题
@Override
public void run(){
while(true){
show();
}
}
/**
* 如果要通过同步方法处理Thread类的线程安全问题(即通过继承创建的线程),
* 那么需要将这个方法设置为静态的,否则这个方法是不唯一的,线程依旧不安全
*/
public static synchronized void show(){//同步监视器:ThreadTest5.class
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+": "+ticket);
ticket--;
}else{
System.exit(0);//退出虚拟机
}
}
}
public class DemoThread6 {
public static void main(String[] args){
ThreadTest5 test1 = new ThreadTest5();
ThreadTest5 test2 = new ThreadTest5();
ThreadTest5 test3 = new ThreadTest5();
test1.setName("窗口1");
test2.setName("窗口2");
test3.setName("窗口3");
test1.start();
test2.start();
test3.start();
}
}
结果为:
2、通过实现Runnable创建的线程:
class ThreadTest3 implements Runnable{
private int ticket = 10;
@Override
public void run () {
while (true) {
// if(ticket > 0)//如果是这样的,那么操作共享数据代码就会少,所以会出现错码
// show();
// else
// break;
show();
}
}
//在返回值之前添加synchronized修饰,从而成为同步方法,使用synchronized时,操作共享数据的代码不可以多,也不可以少
public synchronized void show(){//同步监视器:this
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
}else{
System.exit(0);
}
}
}
public class DemoThread2 {
public static void main(String[] args){
ThreadTest3 test3 = new ThreadTest3();
Thread thread = new Thread(test3);
Thread thread1 = new Thread(test3);
Thread thread2 = new Thread(test3);
thread.setName("窗口1");
thread1.setName("窗口2");
thread2.setName("窗口3");
thread.start();
thread1.start();
thread2.start();
}
}
结果:
方法二:同步代码块实现,从而解决线程的安全问题:
1、通过继承创建的线程:
class Window extends Thread{
private static int ticket = 10;//定义一个私有的静态变量,从而能够被所有对象所共享
static Object obj = new Object();//要将这个同步监视器设置为静态的才可以被多个线程所共享
@Override
public void run(){
//synchronized()括号后面是一个同步监视器,俗称锁,任何一个类的对象都可以作为锁,从而解决了线程的安全问题
while(true) {
synchronized(obj){//同步监视器:obj(任何一个类的对象都可以充当同步监视器)
//synchronized()里面的代码是操作共享数据的代码,涉及到了ticket的判断、赋值等
if (ticket > 0) {
System.out.println(getName() + ": 票数 " + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class DemoThread5 {
public static void main(String[] args){
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();//定义三个窗口
window1.setName("窗口1 ");
window2.setName("窗口2 ");
window3.setName("窗口3 ");
window1.start();//启动线程,调用run方法
window2.start();
window3.start();
}
}
结果:
2、通过实现Runnable接口创建的线程:
class ThreadTest2 implements Runnable{
private int ticket = 10;//由于是只有这个一类作为参数,那么就可以不用共享了
@Override
//重写Runnable类的run方法
public void run() {
while(true){
synchronized(this) {//同步监视器:this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class DemoThreadTest {
public static void main(String[] args){
//新建Runnable类子类对象
ThreadTest2 test = new ThreadTest2();
//构建Thread类对象,其中test作为它的构造方法的参数
Thread thread1 = new Thread(test);
Thread thread2 = new Thread(test);
Thread thread3 = new Thread(test);
thread1.setName("线程1");//调用方法setName,从而设置线程名字
thread2.setName("线程2");//调用方法setName,从而设置线程名字
thread3.setName("线程3");//调用方法setName,从而设置线程名字
//调用thread的start方法,从而启动线程,调用Thread类中的Runnable类型的target的run方法
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
线程的通信
实例:两个线程交替打印0-10的数字
线程的通信中涉及到几个方法:
①wait():一旦执行这个方法,就会将线程1处于阻塞的状态,并且释放同步监视器,从而让其他的线程参与进来。
②notify():一旦执行这个方法,那么就会唤醒被wait的一个线程,如果有多个线程,那么优先级高的线程先被唤醒。
③notifyAll():一旦执行这个方法,那么就会唤醒被wait的所有线程。注意notify()和notifyAll()的区别,从而如果有两个线程的话,那么执行notify()方法就可以实现交替,但是如果有超过3个线程(包括3)那么就要执行notifyAll()方法才可以实现交替。
注意:
①这三个方法都是Object类中的,而不是Thread类的
②这三个方法必须是在同步代码块或者同步方法中使用
③这三个方法是被同步监视器所调用的,也正因如此,更加论证了①的观点,因为同步监视器是任何一个类的对象都可以的,所以无论是哪一个类的对象都可以调用这些方法,因为他们都是Object的子类。
代码:
同步方法中的线程通信:
class Number5 extends Thread{
private static int number = 1;//因为是通过继承实现线程,因此这个变量要被所有的对象共享,就要定义为静态的
@Override
public void run(){
while(true) {
show();
}
}
//在通过继承的线程中,如果要写同步方法,那么这个方法要被static修饰才可以被所有的对象所共享
public static synchronized void show(){//同步监视器:Number5.class
//在通过继承的线程中同步监视器慎用this,但是可以用类名.class充当同步监视器
//调用notify方法,从而唤醒被wait的一个线程,如果有多个被wait的线程,那么优先级高的线程会被唤醒
Number5.class.notifyAll();//如果只有两个线程,那么调用notify就可以实现线程的交替,但是如果有超过3(包括3)个线程的时候,则要调用notifyAll才可以
if(number <= 10){
System.out.println(Thread.currentThread().getName()+": "+ number);
number++;
}else{
System.exit(0);//退出虚拟机
}
try {
//调用wait方法的时候,会抛出异常,因此要用try/catch进行捕获处理
//如果这里没有写同步监视器的话,那么就会默认为this,此时this不是同步监视器,因为同步监视器必须要求是所有的线程共用一个锁,因此从而报错
Number5.class.wait();//同步监视器调用wait方法,从而会是当前的线程处于阻塞的状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class CommunicationTestIherit2 {
public static void main(String[] args){
Number5 num1 = new Number5();
Number5 num2 = new Number5();
num1.setName("线程1");
num2.setName("线程2");
num1.start();
num2.start();
}
}
结果:
同步代码块中的线程通信:
class Number4 extends Thread{
private static int number = 1;//因为是通过继承实现线程,因此这个变量要被所有的对象共享,就要定义为静态的
@Override
public void run(){
while(true){
//在通过继承的线程中同步监视器慎用this,但是可以用类名.class充当同步监视器
synchronized(Number4.class){//同步监视器:Number5.class
//调用notify方法,从而唤醒被wait的一个线程,如果有多个被wait的线程,那么优先级高的线程会被唤醒
Number4.class.notify();//如果只有两个线程,那么调用notify就可以实现线程的交替,但是如果有超过3(包括3)个线程的时候,则要调用notifyAll才可以
if(number <= 10){
System.out.println(Thread.currentThread().getName()+": "+ number);
number++;
}else{
break;
}
try {
//调用wait方法的时候,会抛出异常,因此要用try/catch进行捕获处理
//如果这里没有写同步监视器的话,那么就会默认为this,此时this不是同步监视器,因为同步监视器必须要求是所有的线程共用一个锁,因此从而报错
Number4.class.wait();//同步监视器调用wait方法,从而会是当前的线程处于阻塞的状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class CommunicationTestIherit {
public static void main(String[] args){
Number4 num1 = new Number4();
Number4 num2 = new Number4();
num1.setName("线程1");
num2.setName("线程2");
num1.start();
num2.start();
}
}
结果:
sleep() 和wait()的异同
相同之处:一旦被调用,那么两者都会使线程处于阻塞的状态。
不同之处:
①声明的位置不同:sleep()是在Thread类中生命的,wait()是在Object类中声明的。
②调用的要求不同:sleep()可以在任何的位置中调用,但是wait()方法只能在同步代码块或者同步方法中声明。
③是否释放同步监视器:如果两个方法都在同步方法或者同步代码块中,那么sleep()不会释放同步监视器,而wait()会释放同步监视器,从而让其他的线程参与进来。
如果有说的不好的或者错误的地方,请大家指正,会改正的。