标签:下载文件 不同 返回 线程的生命周期 row 特殊情况 服务器 定义 notifyall
多线程的概述:即同时做多件事情;一个服务器可以让多个人同时访问。
进程的概述:在一个操作系统中,每个独立执行的程序都可称之为一个进程,也就是“正在运行的程序”。
在以上图示中,在一个程序中多个线程执行图,看似同时进行,其实是由CPU调度,CPU的运行速度很快,所以看起来像是同时执行的。
在Java中提供了实现多线程的两种方式:一种是继承java.lang包下的Thread
类,覆写Thread类的run()方法,在run()方法中实现运行在线程上的代码;另一种是实现java.lang.Runnable
接口,同样是在run()方法中实现运行在线程上的代码。
继承Thread类
分析一下单线程和多线程的运行流程:
可以看出,单线程执行时会按照顺序一步步执行,而多线程,在main()方法和run()方法都可以同时运行。
线程开启不一定执行,由CPU调度执行。
public class TestThreadP1 extends Thread{ //继承Thread类
@Override
public void run() { //重写run()方法
//run方法线程体:
for (int i = 0; i < 20; i++) {
System.out.println("run方法线程体" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象:
TestThreadP1 thread = new TestThreadP1();
//调用start()方法,开启线程:
thread.start();
//主方法main线程:
for (int i = 0; i < 2000; i++) {
System.out.println("主方法main线程" + i);
}
}
}
注意:通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类。
实现Runnable接口
与Thread不同,实现Runnable接口,可以解决Java单继承的问题,可以实现多线程,也可以继承于其他的类。
public class TestRunnable implements Runnable { //实现Runnable接口
@Override
//run方法实现体:
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println("Runnable 执行!!!" + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类:
TestRunnable test = new TestRunnable();
//创建线程对象,通过线程对象来开启线程:
Thread thread = new Thread(test);
//开启线程:
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main 执行!!!" + i);
}
}
}
实现Callable接口
当多个线程同时操作同一个资源的情况下,线程不安全,会发生数据紊乱的情况。所以这时候,可以使用实现Callable接口来实现多线程;其可以抛出异常和定义返回值;这部分如果时初学者可以了解一下!到后期还是挺重要的。
public class TestCallable implements Callable<Boolean> { //实现Callable接口
private String url;
private String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call(){
WebDownload2 webdown = new WebDownload2();
webdown.downloader(url,name);
System.out.println("下载文件:" + name);
return true; //返回值
}
}
class WebDownload2{
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建目标对象:
TestCallable t1 = new TestCallable("链接","名字");
TestCallable t2 = new TestCallable("链接","名字");
TestCallable t3 = new TestCallable("链接","名字");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//关闭Callable服务
ser.shutdownNow();
}
}
Lambda表达式
Java中Lambda表达式,虽然说时一种语法糖,但是在多线程中的作用还是非常大的。但是只有一点,如果想要使用Lambda表达式,这个方法必须时函数式接口,不然无法使用Lambda表达式。Lambda表达式的使用让定义函数式接口里的方法更加方便。
函数式接口的定义:
public class TestLambda {
public static void main(String[] args) {
Test test = new Testc();
test.run(100,"小明");
//Lambda表达式:
test = (a,b) -> { //多个参数可以去掉参数类型,但是必须去掉括号
System.out.println(b + "run6--->" + a + "步");
};
test.run(10000,"小麦");
}
}
interface Test{
void run(int a, String b);
}
class Testc implements Test{
@Override
public void run(int a, String b) {
System.out.println(b + "run1--->" + a + "步");
}
}
静态代理模式
静态代理模式,即一个真实对象通过一个代理对象来实现一些具体的方法;一个对象,可以通过例外一个对象来实现某些功能。
public class StaticProxy {
public static void main(String[] args) {
/* residenter rd = null;
rd = ()->{
System.out.println("resident is succeed! == Lambda");
};
rd.resident();*/
new Thread( () -> {
System.out.println("this is lambda!--error");
}).start();
residentc res = new residentc(new You());
res.resident();
}
}
//定义接口:
interface residenter{
void resident();
}
//定义实现类:(真实对象) 实现residenter接口
class You implements residenter{
@Override
public void resident() {
System.out.println("resident is succeed!");
}
}
//定义静态代理:(代理对象) 实现residenter接口
class residentc implements residenter{
private residenter target;
public residentc(residenter target) {
this.target = target;
}
@Override
public void resident() {
before();
this.target.resident();
atfer();
}
private void before() {
System.out.println("暂无此用户!");
}
private void atfer() {
System.out.println("用户注册成功!");
}
}
线程的生命周期
在Java中,任何对象都有生命周期,线程也不例外,它也有自己的生命周期;线程的生命周期一共包含了五大状态。
线程的状态:(五大状态)
线程状态观察:对于线程生命周期的观察。
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("****");
});
//观察状态:NEW
Thread.State state = thread.getState();
System.out.println(state);
//观察状态:RUNNABLE
thread.start();
state = thread.getState();
System.out.println(state);
//只要线程不终止,就会一直输出:
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(100);
state = thread.getState();
System.out.println(state); //TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观察状态:TERMINATED
state = thread.getState();
System.out.println(state);
}
}
对于线程的生命周期,我们只要能够理解线程阻塞的概念、原因就基本没什么问题了。
线程关闭
对于线程中的线程关闭,线程不建议使用自带的stop()和destory()方法,此方法已经被废弃使用了,建议自己定义一个Boolean值,来控制线程的关闭,当这个变量置为false,则终止线程的运行。
public class TestStop implements Runnable {
private boolean flag = true;
private String name;
public TestStop(String name) {
super();
this.name = name;
}
public void stop(){ //定义终止线程的方法
flag = false;
}
@Override
public void run() {
int s = 1;
while (flag){
System.out.println( name + "输出线程~~~~~" + s++);
}
}
public static void main(String[] args) {
TestStop test = new TestStop("线程A:");
new Thread(test).start(); //开启线程
for (int i = 0; i < 1000; i++) {
System.out.println("主线程开启:" + i);
}
test.stop(); //调用终止线程的方法,停止线程
System.out.println("线程已停止。");
}
}
线程休眠:sleep()
暂停线程执行常用的方法有sleep();该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。当前线程调用sleep(long millis)方法后,可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
//模拟倒计时:
public class TestSleep{
public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
testSleep.sleep();
}
public void sleep(){
int num = 10;
while (true){
try {
Thread.sleep(1000);
System.out.println("this is sleep" + num--);
if (num < 0){
System.out.println("stop!");
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让:yield()
暂停线程执行常用的方法有yield()方法;该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。即可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
//测试礼让线程:
public class TestYield {
public static void main(String[] args) {
MyYield my = new MyYield();
new Thread(my,"a:").start();
new Thread(my,"b:").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程启动");
Thread.yield(); //线程礼让
System.out.println(Thread.currentThread().getName() + "线程关闭");
}
}
合并线程:Join()
当在某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。 线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。
//测试Join方法:
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("准备执行Join方法---" + i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
if (i == 200){
try {
thread.join(); //穿插:在200时插入线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("主程序输出---" + i);
}
}
}
}
线程的优先级
在应用程序中,如果要对线程进行调度,最直接的方式就是设置线程的优先级。值得的注意的是,优先级越高的线程获得CPU调度的机会越大,而优先级越低的线程获得CPU调度的机会越小。并不是优先级高就一定先调度。
线程的优先级:优先级使用数字表示,范围为1~10。
下面是获取线程信息的一些基本方法:
//测试线程的优先级:
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "--->" +
Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority,"a");
Thread t2 = new Thread(myPriority,"b");
Thread t3 = new Thread(myPriority,"c");
//设置优先级:int(1-10) 优先级越高越大几率被提 前调度
t1.start();
t2.setPriority(5);
t2.start();
t3.setPriority(10);
t3.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" +
Thread.currentThread().getPriority());
}
}
守护线程(daemon)
守护线程:将线程设置为守护线程后,只要用户线程执行完毕,守护线程也会随之结束。setDaemon()
//测试线程守护:
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Thread thread = new Thread(god);
//开启守护线程 setDaemon()--->目前进入无限循环
thread.setDaemon(true); //默认为false,表示用户线程。
thread.start();
You you = new You();
//开启用户线程,只要用户线程结束,守护线程也会随之结束:
new Thread(you).start();
}
}
class God implements Runnable{
//定义一个守护线程,让其一直循环输出:
@Override
public void run() {
while (true){
System.out.println("This is Daemon!");
}
}
}
class You implements Runnable{
//定义一个用户线程,该线程达到一定结果就会结束:
@Override
public void run() {
for (int i = 0; i <= 36500; i++) {
System.out.println("This is You Pro!");
}
}
}
线程同步与 Lock锁
在Java中,多个线程访问一个对象,会发生错误。确保不会出现这种错误,处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
当多个线程使用同一个共享资源时,可以将处理共享资源的代码放在一个用synchronized修的方法中或一个使用synchronized关键字来修饰的代码块中,这个代码块被称作同步代码块。
线程同步:(synchronized)形成条件---队列+锁-->保证线程的安全性
public synchronized void accessVal();
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。
synchronized(lock)
{
//允许访问控制的代码
}
上面的代码中,lock是一个锁对象,它是同步代码块的关键。当某一个线程执行同步代码块时,其它线程将无法执行当前同步代码块,会发生阻塞,等当前线程执行完同步代码块后,所有的线程开始抢夺线程的执行权,抢到执行权的线程将进入同步代码块,执行其中的代码。
//线程同步的例子
//两个人在银行取同一个账户里面的钱:
public class UnsafeBank {
public static void main(String[] args) {
//账户:
Account account = new Account(150,"Cent");
//第一个取款的人:
Drawingmoney you = new Drawingmoney(account,50,"You");
//第二个取款的人:
Drawingmoney she = new Drawingmoney(account,100,"She");
you.start();
she.start();
}
}
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawingmoney extends Thread{
Account account; //账户
int Dmoney; //取款额度
int Nmoney; //手里额度
public Drawingmoney(Account account, int Dmoney, String name){
super(name);
this.account = account;
this.Dmoney = Dmoney;
}
//取钱
//synchronized 默认锁的是this. 它本身。
@Override
public void run() {
//synchronized代码块:
synchronized (account){
//判断卡里有没有钱:
if (account.money - Dmoney < 0){
System.out.println(this.getName() + "余额不够,无法取款!");
return;
}
try {
Thread.sleep(100); //放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 取款金额
account.money = account.money - Dmoney;
//手里金额 = 当前金额 + 取款金额
Nmoney = Nmoney + Dmoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "手里金额为:" + Nmoney);
}
}
}
死锁: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。因此, 某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
解决方法: 死锁是由于“同步块需要同时持有多个对象锁造成”的,要解决这个问题,就是:同一个代码块,不要同时持有两个对象锁。
//死锁:多个线程互相拥有对方需要的资源,然后形成僵持;
public class SynLock {
public static void main(String[] args) {
//两个对象使用同个资源,造成死锁
Dohomework d1 = new Dohomework(0,"学生1");
Dohomework d2 = new Dohomework(0,"学生2");
d1.start();
d2.start();
}
}
class Pen{
}
class Note{
}
class Dohomework extends Thread{
//需要的资源只有一份,所以使用static
static Pen pen = new Pen();
static Note note = new Note();
int choice; //选择
String student; //人物
Dohomework(int choice, String student){
this.choice = choice;
this.student = student;
}
@Override
public void run() {
try {
dohomewhork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//互相持有对方的锁,需要拿到对方的资源:
private void dohomewhork() throws InterruptedException {
if (choice == 0 ){
synchronized(pen){
System.out.println(this.student + "获得:pen的锁");
Thread.sleep(1000);
// synchronized (note){ //同步中出现两个锁,会造成死锁
// System.out.println(this.student + "再获得:note的锁");
// }
}
//把另外一个同步锁放在第一个同步锁外面就不会造成死锁:
synchronized (note){
System.out.println(this.student + "再获得:note的锁");
}
}else {
synchronized (note){
System.out.println(this.student + "获得:note的锁");
Thread.sleep(2000);
// synchronized (pen){ //同步中出现两个锁,会造成死锁
// System.out.println(this.student + "再获得:pen的锁");
// }
}
synchronized (pen){
System.out.println(this.student + "再获得:pen的锁");
}
}
}
}
线程并发协作(生产者/消费者模式)
多线程环境下,我们经常需要多个线程的并发和协作;生产者/消费者模式: 生产者------缓冲区-------消费者
该模式的好处:
线程并发协作(也叫线程通信),通常用于生产者/消费者模式,常用方法如下:
(都只能在同步方法或者同步代码块中使用,否则会抛出异常。)
//测试:生产消费者模型---->利用缓冲区
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Comsumer(container).start();
}
}
//生产者:
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
//生产:
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
container.push(new Pencil(i));
System.out.println("生产了ID为:" + i + "的铅笔");
}
}
}
//消费者:
class Comsumer extends Thread{
SynContainer container;
public Comsumer(SynContainer container) {
this.container = container;
}
//消费:
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("消费了ID为--->" + container.pop().id + "的铅笔");
}
}
}
//产品:
class Pencil{
int id;
public Pencil(int id) {
this.id = id;
}
}
//缓冲区:
class SynContainer{
//需要一个容器大小:
Pencil[] pencils = new Pencil[10];
//定义一个计数器:
int count = 0;
//生产:
public synchronized void push(Pencil pencil){
while (count == pencils.length){
//产品已满,同之消费者消费,生产者等待。。。。
try {
this.wait(); //让生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满就需要丢入产品:
pencils[count] = pencil;
count++;
this.notifyAll(); //解除等待。可以消费了
}
//消费:
public synchronized Pencil pop(){
while (count == 0){
//产品已空,通知生产者生产,消费者等待。。。。
try {
this.wait(); //消费者等待。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费:
count--;
Pencil pencil = pencils[count];
this.notifyAll(); //解除等待,通知生产者生产
return pencil;
}
}
到这里,多线程的基本知识都总结的差不多了,如果想要进一步了解多线程,可以学习JUC编程。
标签:下载文件 不同 返回 线程的生命周期 row 特殊情况 服务器 定义 notifyall
原文地址:https://www.cnblogs.com/vxzx/p/14743296.html