多线程
一、线程的构造方法
public class ThreadConstructor {
public static void main(String[] args) {
//构造方法:用来创建对象,会调用到构造方法,比如给类成员变量进行赋值
Thread t = new Thread();//用来创建对象拿到线程类Thread类对象名,t,还可以给Thread类里面成员变量字符数组赋值
String name = t.getName();//线程的名字
System.out.println(name);//Thread-0
Thread t1 = new Thread();//用来创建对象拿到线程类Thread类对象名,t1
String name1 = t1.getName();//线程的名字
System.out.println(name1);//Thread-1
Thread t3 = new Thread("线程3");//用来创建对象拿到线程类Thread类对象名,t3,还可以在方法里面指定线程的名字,底层原来用传入的名字给Thread类里面成员变量字符数组赋值
System.out.println(t3.getName());//线程3
t3.setName("自定义线程名字");
System.out.println(t3.getName());
//获取主线程的名字
Thread tm = Thread.currentThread();//得到当前线程对象的名字,当前线程,默认主线程tm
t.setName("主线程");
System.out.println(tm.getName());//main
try {
Thread.sleep(3000);//当前线程睡眠3000毫秒
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println("醒了");
//start()://同一个线程对象不能多次调用start方法开启,否则出现非法线程状态异常:IllegalThreadStateException,之后调用run方法
Thread th1 = new Thread();//
th1.start();
Thread th2 = new Thread();//
th2.start();
}
}
二、自定义线程
1.继承Thread类
/**
1.写一个类去继承Thread线程类
2.重写这个Thread类里面的run方法
3.把我们要做的事情写在这个重写的run方法里面
4.通过Thread线程类的子类对象去调用继承自Thread里面start方法去开启一个线程
*/
public class Thread0 {
public static void main(String[] args) {//入口
//4.通过Thread线程类的子类对象,去调用继承自Thread里面start方法去开启一个线程
MyThread mt = new MyThread();//mt
mt.start();//去调用继承自Thread里面start方法去开启一个线程,需要一定的时间,开启完毕调用start方法底层会帮我们调用重写的run方法
// mt.run();//这样调用可以但是不是多线程,没有调用start方法开启的线程都是子线程,都在主线程这一个线程,没有多个线程,没有多线程的特点,随机性,不要这样写!!!
//join():用线程对象名调用这个方法就让调用方法的当前线程先执行完它的事情,其他线程才能执行
try {
mt.join();//vip
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
//随机性(由于CPU在做高效的切换,一会执行这个线程一会执行那个),延迟性(线程的开启需要时间)
//其实在main里面有个默认的线程,主线程,main主方法所在的线程,其他通过调用start方法开启的线程都是子线程
for (int i = 0; i < 1000; i++) {//主线程
System.out.println("线程实现方式1");
}
}
}
//1.继承Thread线程类
class MyThread extends Thread{
//2.重写这个Thread类里面的run方法
@Override
public void run() {//运行,跑起来
//3.把我们要做的事情写在这个重写的run方法里面
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread");
}
}
}
2.实现Ruannable接口
/**
1.写一个类去实现Runnable接口
2.重写这个Runnable接口里面抽象方法run方法
3.把我们要做的事情写在这个重写的run方法里面
4.通过创建线程类Thread类的对象,调用THread类的构造方法,在构造方法里面传入Runnable接口实现类对象
5.通过创建线程类Thread类的对象名,调用里面start方法去开启一个线程
*/
public class Thread0 {
public static void main(String[] args) {//入口
//4.通过创建线程类Thread类的对象,调用THread类的构造方法,在构造方法里面传入Runnable接口实现类对象
// MyThread2 mt2 = new MyThread2();//
// mt2.run();//这样调用可以但是不是多线程,没有调用start方法开启的线程都是子线程,都在主线程这一个线程,没有多个线程,没有多线程的特点,随机性,不要这样写!!!
// Thread t = new Thread(mt2);//t
Thread t = new Thread(new MyThread2(),"线程1");//多线程实现方式二用到,
//5.通过创建线程类Thread类的对象名,调用里面start方法去开启一个线程
t.start();//去调用继承自Thread里面start方法去开启一个线程,需要一定的时间,开启完毕调用start方法底层会帮我们调用重写的run方法
//随机性(由于CPU在做高效的切换,一会执行这个线程一会执行那个),延迟性(线程的开启需要时间)
//其实在main里面有个默认的线程,主线程,main主方法所在的线程,其他通过调用start方法开启的线程都是子线程
// for (int i = 0; i < 1000; i++) {//主线程
// System.out.println("我爱你");
// }
// System.out.println(t.getName());//Thread-0
}
}
// 1.实现Runnable接口
class MyThread2 implements Runnable{//Thread
//2.重写这个Runnable接口里面抽象方法run方法
@Override
public void run() {//重写的run方法
//3.把我们要做的事情写在这个重写的run方法里面
for (int i = 0; i < 1000; i++) {
System.out.println("Runnable Thread");
}
//我这个类不是Thread类及其子类,想调用Thread类里面getName方法,只要那到一个Thread类东西,比如他的对象名就可以调用方法,这个方法是Thread里面static静态方法,得到一个Thread类的对象名
Thread t = Thread.currentThread();//得到当前线程Thread对象的对象名t
System.out.println(t.getName());
}
}
三、拓展实例
1.同步方法
public class SyncMethod {
public static void main(String[] args) {
Print p = new Print();//p
//多线程实现方式一,继承Thread类,成为的子类,通过线程类子类对象调用start方法去开启一个线程,有这么一个东西可以简单表示一个类的子类对象,匿名内部类
new Thread(){
@Override
public void run() {
while (true) {
p.method();
}
}
}.start();//这个整体就是Thread类的子类对象!!!就可以调用Thread类里面的start方法去快速开启一个线程,接着调用重写的run方法!!!
new Thread(){
@Override
public void run() {
while (true) {
p.method2();
}
}
}.start();//这个整体就是Thread类的子类对象!!!就可以调用Thread类里面的start方法去快速开启一个线程,接着调用重写的run方法!!!
}
}
class Print {
public synchronized void method(){//1234
//同步就可以让CPU在一定时间内,只让一个线程进来搞事情,其他线程不能进来
// synchronized (Integer.class) {//谁来调用我我就代表谁
// System.out.print("1");
// System.out.print("2");
// System.out.print("3");
// System.out.print("4");
// System.out.println();
// }
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.println();
}
//同步方法:synchronized同步关键字修饰的方法,同步方法也有锁,把里面代码锁起来,达到同步的效果
//非静态同步方法:也有锁才能锁起来达到同步,锁对象是this
//静态同步方法:静态的同步方法的锁对象是这个方法所在的类的字节码对象,类名.class
public synchronized void method2(){//一二三四
// synchronized (Print.class) {//要达到同步,要求这里的锁对象跟上面的锁对象是同一把锁!!!this p
// System.out.print("一");
// System.out.print("二");
// System.out.print("三");
// System.out.print("四");
// System.out.println();
// }
System.out.print("一");
System.out.print("二");
System.out.print("三");
System.out.print("四");
System.out.println();
}
}
2.死锁问题
/**
* 死锁:
原理:两个线程几乎同时开启,第一个线程拿到锁,第二个线程拿到另外一把锁,双方都不肯释放锁,
造成双方互相抢占资源造成互相等待的现象,死锁
如何避免:不用使用同步代码块的嵌套(就是在一个同步代码块又写了一个同步代码块)
*/
public class DeadLock {
//两把锁
public static final String LOCKA = "锁A";//一只筷子
public static final String LOCKB = "锁B";//另外一只筷子
public static void main(String[] args) {
//两个线程几乎同时开启,两把锁,同步代码块的嵌套(就是在一个同步代码块又写了一个同步代码块),线程实现方式一继承Thread类,成为子类,创建子类对象,匿名内部类表示一个类子类对象简化格式
new Thread(){
@Override
public void run() {
//最后一步,用循环加以改进,因为希望下面的代码不断调用,互相强占的效果
while (true) {
//同步代码块的嵌套(就是在一个同步代码块又写了一个同步代码块)
synchronized (LOCKA) {//其实是这样的死锁面试题.LOCKA,不写也是写,系统给你写,只要你的静态变量或者方法在本类中,省略类名.
System.out.println("拿到"+LOCKA+"等待"+LOCKB);
synchronized (LOCKB) {
System.out.println("拿到"+LOCKB+"开吃");//
}
}//locka什么时候释放,做完事情就释放lockasynchronized{开始到}结束释放
}
// //同步代码块的嵌套(就是在一个同步代码块又写了一个同步代码块)
// synchronized (LOCKA) {//其实是这样的死锁面试题.LOCKA,不写也是写,系统给你写,只要你的静态变量或者方法在本类中,省略类名.
// System.out.println("拿到"+LOCKA+"等待"+LOCKB);
// synchronized (LOCKB) {
// System.out.println("拿到"+LOCKB+"开吃");//
// }
// }//locka什么时候释放,做完事情就释放lockasynchronized{开始到}结束释放
}
}.start();//重写run方法
new Thread(){
@Override
public void run() {
//最后一步,用循环加以改进,因为希望下面的代码不断调用,互相强占的效果
while (true) {
synchronized (LOCKB) {//其实是这样的死锁面试题.LOCKA,不写也是写,系统给你写,只要你的静态变量或者方法在本类中,省略类名.
System.out.println("拿到"+LOCKB+"等待"+LOCKA);
synchronized (LOCKA) {
System.out.println("拿到"+LOCKA+"开吃");//
}
}//locka什么时候释放,做完事
}
// synchronized (LOCKB) {//其实是这样的死锁面试题.LOCKA,不写也是写,系统给你写,只要你的静态变量或者方法在本类中,省略类名.
// System.out.println("拿到"+LOCKB+"等待"+LOCKA);
// synchronized (LOCKA) {
// System.out.println("拿到"+LOCKA+"开吃");//
// }
// }//locka什么时候释放,做完事情就释放lockasynchronized{开始到}结束释放
}
}.start();//重写run方法
}
}
3.卖票问题
/**
*
* 多线程模拟卖票:卖票,进行中程序进程,分四条路径(线程)去做,分四个窗口(线程),开启四个线程对象去卖票,共同去卖100张票
*/
public class Tickets {
public static void main(String[] args) {//入口
Window m = new Window("窗口一");
m.start();//start底层帮我调用重写的run方法!!!
Window m2 = new Window("窗口二");
m2.start();//start底层帮我调用重写的run方法!!!
Window m3 = new Window("窗口三");
m3.start();//start底层帮我调用重写的run方法!!!
Window m4 = new Window("窗口四");
m4.start();//start底层帮我调用重写的run方法!!!
// System.out.println(Window.class);
// System.out.println(Window.class);
System.out.println(new Object());//new 新建,创建一个新的,不是原来的额那一个
System.out.println(new Object());
}
}
//写一个类来模拟窗口线程类,多线程实现方式一,写一个类成为Thread的子类
class Window extends Thread {//开启四个线程对象窗口类对象去卖票,共同去卖100张票,共享,被窗口类的所有对象所共享,用static修饰
static int ticket = 100;//所有对象都使用同一份!!!99 98,1
public Window(String name){//name = "窗口?";
super(name);//表示调用父类Thread的无参的构造方法Thread(name);
}
@Override
public void run() {//重写的run方法在这里!!!
//我希望不断的卖票,就要不断的做事情,用循环,直到票数小于等于0用break跳出循环才停止
while (true) {//this,谁来调用我,我就代表谁!!!this..m m2,m3,m4;
//出现线程安全性问题,用同步来解决://四个线程
//同步:同步就可以让CPU在某段时间内只让一个线程进来做事情,其他线程不能进来,等你做完了,才能一个个进行做事情,理解排队单线程的,先用同步代码块
synchronized (Window.class) {//()需要的是同步代码块的锁对象,只让一个线程进来搞事情
//锁对象可以是任意的,但是这个同步代码块被多次调用的时候,要求用的是同一个把锁才能锁起来,锁是唯一的,暂时用字节码对象来表示,类名.class,
//字节码对象对应就是类,类只加载一次,所以字节码对象在内存中只有一个,唯一的
//直到票数小于等于0用break跳出循环才停止
if (ticket<=0) {
break;//跳出整个循环,循环不再执行,下面的代码不走了
}
//编程是为了更好模拟卖票,卖票是需要一定的时间的,消耗一定的时间,用睡眠模拟一下
try {
Thread.sleep(300);//线程一睡着了,线程二睡着,线程三睡着了,线程四睡着了
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}//出现线程的安全性问题,不是我想要的数据,比如出现同票,0票,负票,原因在于有多个线程在操作同一个共享数据ticket
//醒了,
System.out.println(getName()+"正在卖"+ticket--+"张票");//Thread-0
}//什么时候是否锁?做完事情就释放,从同步代码块synchronized{开始到}结束
// //直到票数小于等于0用break跳出循环才停止
// if (ticket<=0) {
// break;//跳出整个循环,循环不再执行,下面的代码不走了
// }
//
// //编程是为了更好模拟卖票,卖票是需要一定的时间的,消耗一定的时间,用睡眠模拟一下
// try {
// Thread.sleep(300);//线程一睡着了,线程二睡着,线程三睡着了,线程四睡着了
// } catch (InterruptedException e) {
// // TODO 自动生成的 catch 块
// e.printStackTrace();
// }//出现线程的安全性问题,不是我想要的数据,比如出现同票,0票,负票,原因在于有多个线程在操作同一个共享数据ticket
// //醒了,
// System.out.println(getName()+"正在卖"+ticket--+"张票");//Thread-0
}
}
}
4.图片下载
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
/**
*
* URL类:
概述:类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针(代表网上资源地址(路径)),相对于字符串路径封装File对象
比如我想下载网上一张图片,它的url地址是:"http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg";
*/
public class SingleThreadDownloader {
public static void main(String[] args) throws Exception {
String path = "http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg";//10个字节
//快速得到网上文件的总的字节大小,通过URL类对象打开连接调用得到(文件)内容大小的方法
int length = MutiThreadDownloader.getFileslength(path);
MutiThreadDownloader std = new MutiThreadDownloader(path,0,(length/2)-1,1);//0,(10/2)-1;
std.start();//run
MutiThreadDownloader std2 = new MutiThreadDownloader(path,(length/2),2*(length/2)-1,2);//(10/2), 2*(10/2)-1;
std2.start();//run
// std.join();
// std2.join();
//只要我的子线程还没有下载完毕,不走文件合并代码
while (MutiThreadDownloader.count>0) {
//子线程还没有下载完毕,不走文件合并代码主线程逻辑,让主线程在这里睡觉
Thread.sleep(10);//让当前线程睡觉,主线程
}
//要分工合作,为每个线程进行编号,比如1号下载前面字节生产文件的图片名字1.jpg,2号下载后面字节生产文件的图片名字2.jpg
//合并两个图片为一个新的图片,合并文件,很简单,只要你告诉我要合并的文件所在文件夹,打开文件夹,得到下面东西是文件并且.jpg结尾文件,通过输入流读取文件,
//通过输出流把读取内容写到新的文件,追加
File dir = new File("D:\\下载器");
MutiThreadDownloader.mergeFiles(dir);//主线程
}
}
class MutiThreadDownloader extends Thread {
private String path;//null,"http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg";
private int startindex;//0,线程下载的开始索引
private int endindex;//0,线程下载的结束索引3
private int id;//0,1,2
public static int count;//子线程的个数,0
public MutiThreadDownloader(String path,int startindex,int endindex,int id){//path = "http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg";
this.path = path;
this.startindex = startindex;
this.endindex = endindex;
this.id = id;
count++;//2
}
@Override
public void run() {
//下载网络一张图片,图片地址http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg
// String path = "http://pic4.nipic.com/20091217/3885730_124701000519_2.jpg";
//创建URL类对象,打开连接,得到输入流,用来读取网上文件,通过输出流把读取到内容写到本地的关联文件里面
try {
URL url = new URL(path);//url
URLConnection con = url.openConnection();//con连接对象
//连接对象con设置请求属性方法,可以指定线程下载网上文件的字节范围,其实网上文件也是字节,为每个字节进行编号,0索引对应的文件的第一个字节,其他以此类推
// con.setRequestProperty("Range", "bytes=0-3");//指定线程现在的访问包括0索引到3索引总共四个字节
con.setRequestProperty("Range", "bytes="+startindex+"-"+endindex);//指定线程现在的访问包括0索引到3索引总共四个字节
InputStream fis = con.getInputStream();//多态,输入流对象名fis
// InputStream fis = url.openStream();//这个代码就是上面两行代码缩写,快速得到输入流对象
//通过输出流把读取到内容写到本地的关联文件里面
FileOutputStream fos = new FileOutputStream(id+".jpg");//fos
int ch;
byte[] arr = new byte[1024];
while ((ch=fis.read(arr))!=-1) {
fos.write(arr, 0, ch);
}
fis.close();
fos.close();
System.out.println(id+"号线程下载完毕");
count--;//
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//快速得到网上文件的总的字节大小,通过URL类对象打开连接,调用得到(文件)内容大小的方法
public static int getFileslength(String path) throws Exception, IOException{
return new URL(path).openConnection().getContentLength();
}
//合并两个图片为一个新的图片,合并文件,很简单,只要你告诉我要合并的文件所在文件夹,打开文件夹,得到下面东西是文件并且.jpg结尾文件,通过输入流读取文件,
//通过输出流把读取内容写到新的文件,追加
public static void mergeFiles(File dir) throws Exception{//要合并的文件所在文件夹路径,File类型路径dir
File[] arr2 = dir.listFiles(new FileFilter() {//打开一个文件夹进行过滤
@Override
public boolean accept(File pathname) {//1.jpg,2.jpg 路径
// TODO 自动生成的方法存根
return pathname.isFile()&&pathname.getName().endsWith(".jpg");//下面东西是文件并且.jpg结尾文件,通过输入流读取文件,
}
});
if (arr2!=null) {
for (File file : arr2) {//file 1.jpg,2.jpg 路径
//通过输入流读取文件,通过输出流把读取内容写到新的文件,追加
//通过输出流把读取到内容写到本地的关联文件里面
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("merge.jpg",true);//fos
int ch;
byte[] arr = new byte[1024];
while ((ch=fis.read(arr))!=-1) {
fos.write(arr, 0, ch);
}
fis.close();
fos.close();
}
System.out.println("合并文件成功");
}
}
}