深入浅出Java多线程

发表于 4年以前  | 总阅读数:662 次

链接:blog.csdn.net/weixin_44104367/article/details/104481510

线程

线程的概念,百度是这样解释的:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

1.1 线程与进程的区别

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位

也就是,进程可以包含多个线程,而线程是程序执行的最小单位。

1.2 线程的状态

  1. NEW:线程刚创建
  2. RUNNABLE: 在JVM中正在运行的线程,其中运行状态可以有运行中RUNNING和READY两种状态,由系统调度进行状态改变。
  3. BLOCKED: 线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行
  4. WAITING : 等待状态
  5. TIMED_WAITING: 调用sleep() join() wait()方法可能导致线程处于等待状态
  6. TERMINATED: 线程执行完毕,已经退出

1.3 Notify 和 wait :

Notify 和 wait 的作用 首先看源码给出的解释,这里翻译了一下:

Notify

唤醒一个正在等待这个对象的线程监控。如果有任何线程正在等待这个对象,那么它们中的一个被选择被唤醒。选择是任意的,发生在执行的酌情权。一个线程等待一个对象通过调用一个{@code wait}方法进行监视。

notify()需要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁wait> 导致当前线程等待,直到另一个线程调用

{@link java.lang.Object#notify()}方法或 {@link java.lang.Object#notifyAll()}方法。 换句话说,这个方法的行为就像它简单一样 执行调用{@code wait(0)}。 当前线程必须拥有该对象的监视器。线程 释放此监视器的所有权,并等待另一个线程 通知等待该对象的监视器的线程唤醒 通过调用{@code notify}方法或 {@code notifyAll}方法。然后线程等待,直到它可以重新取得监视器的所有权,然后继续执行。

wait()的作用是使当前执行代码的线程进行等待,它是Object类的方法,该方法用来将当前线程置入预执行队列中,并且在wait所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。

wait 和 sleep 的区别。

它们最大本质的区别是:sleep()不释放同步锁,wait()释放同步锁. 还有用法的上的不同是:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤起.

wait和sleep的区别还有:

  • 这两个方法来自不同的类分别是Thread和Object
  • 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

1.4 Thread.sleep() 和Thread.yield()的异同

1. 相同 : sleep()和yield()都会释放CPU。

2. 不同: sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会。

1.5 补充:死锁的概念

死锁:指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 死锁产生的四个必要条件(缺一不可):

  1. 互斥条件:顾名思义,线程对资源的访问是排他性,当该线程释放资源后下一线程才可进行占用
  2. 请求和保持:简单来说就是自己拿的不放手又等待新的资源到手。 线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
  3. 不可剥夺:在没有使用完资源时,其他线性不能进行剥夺
  4. 循环等待:一直等待对方线程释放资源

我们可以根据死锁的四个必要条件破坏死锁的形成

1.6 补充:并发和并行的区别

并发:是指在某个时间段内,多任务交替的执行任务。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。在一个时间段的线程代码运行时,其它线程处于挂起状

并行:是指同一时刻同时处理多任务的能力。当有多个线程在操作时,cpu同时处理这些线程请求的能力。

区别就在于CPU是否能同时处理所有任务,并发不能,并行能

1.7 补充:线程安全三要素

原子性:Atomic包、CAS算法、synchronized、Lock 可见性:synchronized、volatile(不能保证原子性) 有序性:happens-before规则

1.8 补充:如何实现线程安全

  • 互斥同步:synchronized、lock
  • 非阻塞同步:CAS
  • 无需同步的方案:如果一个方法本来就不涉及共享数据,那它自然就无需任何同步操作去保证正确性
1.9 补充:保证线程安全的机制:
  1. synchronized关键字
  2. lock
  3. CAS、原子变量
  4. ThreadLocl:简单来说就是让每个线程,对同一个变量,都有自己的独有副本,每个线程实际访问的对象都是自己的,自然也就不存在线程安全问题了。
  5. volatile
  6. CopyOnWrite写时复制

多线程

随着CPU核心的增多以及互联网迅速发展,单线程的程序处理速度越来越跟不上发展速度和大数据量的增长速度,多线程应运而生,充分利用CPU资源的同时,极大提高了程序处理速度。

2 创建线程的方法

  • 继承Thread类
public class ThreadCreateTest {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
    }
}
  • 实现Runable接口
public class RunableCreateTest {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        new Thread(runnable).start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
    }
}
  • 通过Callable和Future创建线程
public class CallableCreateTest {
    public static void main(String[] args) throws Exception {

        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();


        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
    }
}


class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");

        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        Thread.sleep(5000);

        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
        return sum;
    }
}
  • 线程池方式创建

实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承,但可以多实现啊),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。 实际开发中,阿里巴巴开发插件一直提倡使用线程池创建线程,原因在下方会解释,所以上面的代码我就只简写了一些demo

2.1 线程池创建线程

线程池,顾名思义,线程存放的地方。和数据库连接池一样,存在的目的就是为了较少系统开销,主要由以下几个特点:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗(主要)。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。

Java提供四种线程池创建方式:

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

通过源码我们得知ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService

public class ThreadPoolExecutor extends AbstractExecutorService

public abstract class AbstractExecutorService implements ExecutorService

2.2 ThreadPoolExecutor介绍

实际项目中,用的最多的就是ThreadPoolExecutor这个类,而《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

我们从 ThreadPoolExecutor入手多线程创建方式,先看一下线程池创建的最全参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明如下:

  • corePoolSize: 线程池的核心线程数,即便线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
  • maximumPoolSize: 最大线程数,不管提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
  • keepAliveTime: 线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
  • unit: 这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
  • BlockingQueue: 一个阻塞队列,提交的任务将会被放到这个队列里。
  • threadFactory: 线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
  • handler: 拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。
2.2.1BlockingQueue

对于BlockingQueue个人感觉还需要单独拿出来说一下

BlockingQueue:阻塞队列,有先进先出(注重公平性)和先进后出(注重时效性)两种,常见的有两种阻塞队列:ArrayBlockingQueueLinkedBlockingQueue

队列的数据结构大致如图:

队列一端进入,一端输出。而当队列满时,阻塞。BlockingQueue核心方法:1. 放入数据 put2. 获取数据take。常见的两种Queue:

2.2.2 ArrayBlockingQueue

基于数组实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

一段代码来验证一下:

package map;

  import java.util.concurrent.*;

  public class MyTestMap {

      private static final int maxSize = 5;
      public static void main(String[] args){
          ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(maxSize);
          new Thread(new Productor(queue)).start();
          new Thread(new Customer(queue)).start();
      }
  }

  class Customer implements Runnable {
      private BlockingQueue<Integer> queue;
      Customer(BlockingQueue<Integer> queue) {
          this.queue = queue;
      }

      @Override
      public void run() {
          this.cusume();
      }

      private void cusume() {
          while (true) {
              try {
                  int count = (int) queue.take();
                  System.out.println("customer正在消费第" + count + "个商品===");

                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }

  class Productor implements Runnable {
      private BlockingQueue<Integer> queue;
      private int count = 1;
      Productor(BlockingQueue<Integer> queue) {
          this.queue = queue;
      }

      @Override
      public void run() {
          this.product();
      }
      private void product() {
          while (true) {
              try {
                  queue.put(count);
                  System.out.println("生产者正在生产第" + count + "个商品");
                  count++;
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }
2.2.3 LinkedBlockingQueue

基于链表的阻塞队列,内部也维护了一个数据缓冲队列。需要我们注意的是如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

2.2.4 LinkedBlockingQueueArrayBlockingQueue的主要区别
  • ArrayBlockingQueue的初始化必须传入队列大小,LinkedBlockingQueue则可以不传入
  • ArrayBlockingQueue用一把锁控制并发,LinkedBlockingQueue俩把锁控制并发,锁的细粒度更细。即前者生产者消费者进出都是一把锁,后者生产者生产进入是一把锁,消费者消费是另一把锁。
  • ArrayBlockingQueue采用数组的方式存取,LinkedBlockingQueue用Node链表方式存取
2.2.5 handler拒绝策略

java提供了4种丢弃处理的方法,当然你也可以自己实现,主要是要实现接口:RejectedExecutionHandler中的方法- AbortPolicy:不处理,直接抛出异常。

  • CallerRunsPolicy:只用调用者所在线程来运行任务,即提交任务的线程。
  • DiscardOldestPolicy:LRU策略,丢弃队列里最近最久不使用的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉,不抛出异常。
2.2.6 线程池五种状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
  • RUNNING:在这个状态的线程池能判断接受新提交的任务,并且也能处理阻塞队列中的任务
  • SHUTDOWN:处于关闭的状态,该线程池不能接受新提交的任务,但是可以处理阻塞队列中已经保存的任务,在线程处于RUNNING状态,调用shutdown()方法能切换为该状态。
  • STOP:线程池处于该状态时既不能接受新的任务也不能处理阻塞队列中的任务,并且能中断现在线程中的任务。当线程处于RUNNING和SHUTDOWN状态,调用shutdownNow()方法就可以使线程变为该状态
  • TIDYING:在SHUTDOWN状态下阻塞队列为空,且线程中的工作线程数量为0就会进入该状态,当在STOP状态下时,只要线程中的工作线程数量为0就会进入该状态。
  • TERMINATED:在TIDYING状态下调用terminated()方法就会进入该状态。可以认为该状态是最终的终止状态

回到线程池创建ThreadPoolExecutor,我们了解了这些参数,再来看看ThreadPoolExecutor的内部工作原理:

  1. 判断核心线程是否已满,是进入队列,否:创建线程
  2. 判断等待队列是否已满,是:查看线程池是否已满,否:进入等待队列
  3. 查看线程池是否已满,是:拒绝,否创建线程
2.3 深入理解ThreadPoolExecutor

进入execute方法可以看到:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();

        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }

        else if (!addWorker(command, false))
            reject(command);
    }

addWorker方法:

  • 创建Worker对象,同时也会实例化一个Thread对象。在创建Worker时会调用threadFactory来创建一个线程。
  • 启动启动这个线程
2.3.1线程池中ctl属性的作用是什么?

看源码第一反应就是这个CTL到底是个什么东东?有啥用?一番研究得出如下结论: ctl属性包含两个概念:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

private static int ctlOf(int rs, int wc) { return rs | wc; }
  1. runState:即rs 表明当前线程池的状态,是否处于Running,Shutdown,Stop,Tidying,
  2. workerCount:即wc 表明当前有效的线程数

我们点击workerCount即工作状态记录值,以RUNNING为例,RUNNING = -1 << COUNT_BITS;,即-1无符号左移COUNT_BITS位,进一步我们得知COUNT_BITS位29,因为Integer位数为31位(2的五次方减一

private static final int COUNT_BITS = Integer.SIZE - 3;

既然是29位那么就是Running的值为

1110 0000 0000 0000 0000 0000 0000 0000 
|||
31~29位

那低28位呢,就是记录当前线程的总线数啦

private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

从上述代码可以看到workerCountOf这个函数传入ctl之后,是通过ctl&CAPACITY操作来获取当前运行线程总数的。也就是RunningState|WorkCount&CAPACITY,算出来的就是低28位的值。因为CAPACITY得到的就是高3位(29-31位)位0,低28位(0-28位)都是1,所以得到的就是ctl中低28位的值。

runStateOf这个方法的话,算的就是RunningState|WorkCount&CAPACITY,高3位的值,因为CAPACITY是CAPACITY的取反,所以得到的就是高3位(29-31位)为1,低28位(0-28位)为0,所以通过&运算后,所得到的值就是高3为的值。

简单来说就是ctl中是高3位作为状态值,低28位作为线程总数值来进行存储。 总之这是个

2.3.2 shutdownNow和shutdown的区别

看源码发现有两种近乎一样的方法,shutdownNow和shutdown,设计者这么设计自然是有它的道理,那么这两个方法的区别在哪呢?

shutdown会把线程池的状态改为SHUTDOWN,而shutdownNow把当前线程池状态改为STOP

shutdown只会中断所有空闲的线程,而shutdownNow会中断所有的线程。

shutdown返回方法为空,会将当前任务队列中的所有任务执行完毕;而shutdownNow把任务队列中的所有任务都取出来返回。

2.3.3 线程复用原理
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); 
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();

                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

就是任务在并不只执行创建时指定的firstTask第一任务,还会从任务队列的中自己主动取任务执行,而且是有或者无时间限定的阻塞等待,以保证线程的存活。 默认的是不允许

2.4 CountDownLatch和CyclicBarrier区别:
  1. countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
  2. CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

3. 多线程间通信的几种方式

提及多线程又不得不提及多线程通信的机制。首先,要短信线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析:

题目:有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作

3.1 使用 volatile 关键字
package thread;

public class MyThreadTest {

  public static void main(String[] args) throws Exception {

    notifyThreadWithVolatile();

  }


  private static volatile boolean flag = false;

  private static void notifyThreadWithVolatile() throws Exception {
    Thread thc = new Thread("线程A"){
      @Override
      public void run() {
        for (int i = 0; i < 10; i++) {
          if (i == 5) {
            flag = true;
            try {
              Thread.sleep(500L);
            } catch (InterruptedException e) {

              e.printStackTrace();
            }
            break;
          }
          System.out.println(Thread.currentThread().getName() + "====" + i);
        }
      }
    };

    Thread thd = new Thread("线程B") {
      @Override
      public void run() {
        while (true) {

          while (flag) {
            System.out.println(Thread.currentThread().getName() + "收到通知");
            System.out.println("do something");
            try {
              Thread.sleep(500L);
            } catch (InterruptedException e) {

              e.printStackTrace();
            }
            return ;
          }

        }
      }
    };

    thd.start();
    Thread.sleep(1000L);
    thc.start();

  }
}

个人认为这是基本上最好的通信方式,因为A发出通知B能够立马接受并dosomething

3.2 锁机制

加锁机制无非还是synchronized关键字或者JUC下的lock,这两者的异同以及使用synchronized锁机制 之 代码块锁

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/WFKBNxaYJbJ8-hGJ5L03Qw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Java 中验证时间格式的 4 种方法 2年以前  |  3806次阅读
Java经典面试题答案解析(1-80题) 4年以前  |  3589次阅读
CentOS 配置java应用开机自动启动 4年以前  |  2758次阅读
IDEA依赖冲突分析神器—Maven Helper 4年以前  |  2744次阅读
SpringBoot 控制并发登录的人数教程 4年以前  |  2423次阅读
 目录